/*
===========================================================================
Copyright (C) 2004-2006 Tony J. White

This file is part of Tremulous.

This shrubbot implementation is the original work of Tony J. White.

Contains contributions from Wesley van Beelen, Chris Bajumpaa, Josh Menke,
and Travis Maurer.

The functionality of this code mimics the behaviour of the currently
inactive project shrubet (http://www.etstats.com/shrubet/index.php?ver=2)
by Ryan Mannion.   However, shrubet was a closed-source project and 
none of it's code has been copied, only it's functionality.

Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Tremulous is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "g_local.h"

// big ugly global buffer for use with buffered printing of long outputs
static char g_bfb[ 32000 ];


// note: list ordered alphabetically
g_admin_cmd_t g_admin_cmds[ ] = 
  {
    {"adjustban", G_admin_adjustban, "ban",
      "change the length or reason of a ban.  time is specified as numbers "
      "followed by units 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes),"
      " or seconds if no units are specified",
    },

    
    {"admintest", G_admin_admintest, "information",
      "display your current admin level",
      "", -1
    },

    {"allowbuild", G_admin_denybuild, "player_control",
      "restore a player's ability to build",
      "[^3name|slot#^7]", -1
    },
    
    {"allready", G_admin_allready, "game_control",
      "makes everyone ready in intermission",
      "", -1
    },

    {"ban", G_admin_ban, "ban",
      "ban a player by IP and GUID with an optional expiration time and reason."
      "  time is specified as numbers followed by units 'w' (weeks), 'd' "
      "(days), 'h' (hours) or 'm' (minutes), or seconds if no units are "
      "specified",
      "[^3name|slot#|IP^7] (^5time^7) (^5reason^7)", -1
    },
    
    {"blibset", G_admin_botlibSetVariable, "botlib",
    	"Set a botlib variable. This will often effect bot behavior. It should, anyways.",
    	"<key> <value>", -1
    },
    

    {"bot", G_admin_bot, "bots",
    	"Controls bots in the game. Warning, this is highly experimental, and only works on some maps"
    	", so be careful with it.",
    	"<add/del> <name> [team] (charactor) (skill)", -1
    },
    
    {"botcmd", G_admin_botcmd, "bots",
          "Not implemented, don't use it.",
          "[^3name^7] [^3command^7]", -1
        },
    
    {"buildstatus", G_admin_buildstatus, "information",
            	"A dummy function so that people don't keep calling ^3!buildstatus^7 when they want ^3/buildstatus^7",
            	"", -1
            },
    
    {"clinic", G_admin_clinic, "information",
    	"Show everyone the next clinic time.",
    	"", -1
    },
    
    {"cancelvote", G_admin_cancelvote, "game_control",
      "cancel a vote taking place",
      "", -1
    },

    {"denybuild", G_admin_denybuild, "player_control",
      "take away a player's ability to build",
      "[^3name|slot#^7]", -1
    },

    {"designate", G_admin_designate, "build",
      "give the player designated builder privileges",
      "[^3name|slot#^7]", -1
    },
    
    {"devmap", G_admin_devmap, "game_control",
      "load a map with cheats (and optionally force layout)",
      "[^3mapname^7] (^5layout^7)", -1
    },

    {"gamestats", G_admin_gamestats, "information",
    	"Displays the stats for a game after it has finished",
    	"", -1
    },
    
    {"gamestatus", G_admin_gamestatus, "information",
        	"A dummy function to keep people from calling ^3'!gamestatus'^7 when they meant '/gamestatus'",
        	"", -1
        },
        
    {"give", G_admin_give, "cheats",
      "give player funds (credits or evos)",
      "[^3name^7] (^5amount^7)", -1
    },
    
    {"help", G_admin_help, "information",
      "display commands available to you or help on a specific command",
      "(^5command^7)", -1
    },

    {"info", G_admin_info, "information",
      "read the server's information files",
      "(^5subject^7)", -1
    },

    {"kick", G_admin_kick, "player_control",
      "kick a player with an optional reason",
      "(^5reason^7)", -1
    },
    
    {"L1", G_admin_L1, "admin_control",
      "Sets a level 0 to level 1",
      "[^3name|slot#^7]", -1
    },
    
    {"layoutload", G_admin_layoutload, "game_control",
    	"load a map layout, preserving the current teams and game time",
    	"[^3layoutname^7]", -1
    },
    
    {"layoutdelete", G_admin_layoutdelete, "server_control",
    	"delete a map layout.",
    	"[^3layoutname^7]", -1
    },
    
    {"layoutsave", G_admin_layoutsave, "server_control",
      "save a map layout",
      "[^3mapname^7]", -1
    },
    
    {"listadmins", G_admin_listadmins, "information",
      "display a list of all server admins and their levels",
      "(^5name|start admin#^7) (^5minimum level to display^7)", -1
    },
    
    {"listlayouts", G_admin_listlayouts, "information",
      "display a list of all available layouts for a map",
      "(^5mapname^7)", -1
    },

    {"listplayers", G_admin_listplayers, "player_control",
      "display a list of players, their client numbers and their levels",
      "", -1
    },
    
     {"listmaps", G_admin_listmaps, "information",
      "display a list of available maps on the server",
      "", -1
    },
    
    {"lock", G_admin_lock, "game_control",
      "lock a team to prevent anyone from joining it",
      "[^3a|h^7]", -1
    },
    
    {"map", G_admin_map, "game_control",
      "load a map (and optionally force layout)",
      "[^3mapname^7] (^5layout^7)", -1
    },

    {"mumble", G_admin_mumble, "information",
    	"Show everyone the mumble server information",
    	"", -1
    },
    
    {"mute", G_admin_mute, "player_control",
      "mute a player",
      "[^3name|slot#^7]", -1
    },
    
    {"namelog", G_admin_namelog, "player_control",
      "display a list of names used by recently connected players",
      "(^5name^7)", -1
    },

    {"nextmap", G_admin_nextmap, "server_control",
      "go to the next map in the cycle",
      "", -1
    },

    {"normalize", G_admin_normalize, "server_control",
    	"normalize the game server settings. Normalized settings means "
    	"setting gravity, instant build, killer HP, tellBuildingDestroyed, etc "
    	"are all their default values. Recommended after an admin changes things "
    	"for training purposes and leaves the server",
    	"", -1
    },
    
    {"passvote", G_admin_passvote, "game_control",
      "pass a vote currently taking place",
      "", -1
    },

    {"playerstats", G_admin_playerstats, "information",
    	"show the stats of a given player, as if you were them and you typed '/mystats'",
    	"[^3name^7]", -1
    },
    
    {"putteam", G_admin_putteam, "player_control",
      "move a player to a specified team",
      "[^3name|slot#^7] [^3h|a|s^7]", -1
    },

    {"rcon", G_admin_rcon, "server_control",
      "dummy command, keeps me from handing out my password when I do an !rcon",
      "", -1
    },
    
    {"readconfig", G_admin_readconfig, "server_control",
      "reloads the admin config file and refreshes permission flags",
      "", -1
    },
    
    {"register", G_admin_register, "self_control",
      "Registers your name to protect it from being used by others or updates your admin name to your current name.",
      "", -1
    },

    {"rename", G_admin_rename, "player_control",
      "rename a player",
      "[^3name|slot#^7] [^3new name^7]", -1
    },

    {"report", G_admin_report, "self_control",
    	"Reports potential admin abuse. Use this sparingly, as all reports are investigated "
    	"and frivilous uses of ^2!report^7 will not be taken lightly. A console dump of the "
    	"session will automatically be saved - use the reason field to point out where in the "
    	"report the specific abuse happened",
    	"[^3name|slot#^7] [^3reason^7]", -1
    },
    
    {"restart", G_admin_restart, "server_control",
      "restart the current map (optionally using named layout or keeping/switching teams)",
      "(^5layout^7) (^5keepteams|switchteams|keepteamslock|switchteamslock^7)", -1
    },

    {"setlevel", G_admin_setlevel, "server_control",
      "sets the admin level of a player",
      "[^3name|slot#|admin#^7] [^3level^7]", -1
    },

    {"setstage", G_admin_setstage, "game_control",
    	"sets the current stage of humans or aliens",
    	"[^3team^7] [^3level^7]", -1
    },
    
    {"setbp", G_admin_setbp, "game_control",
    	"changes the number of build points of humans or aliens",
    	"[^3team^7] [^3bp^7]", -1
    },
 
    {"settime", G_admin_settime, "game_control",
    	"sets the current game time. Useful for avoiding SD",
    	"", -1
    },
    
    {"showbans", G_admin_showbans, "bans",
      "display a (partial) list of active bans",
      "(^5start at ban#^7)", -1
    },

    {"showentities", G_admin_showentities, "debugging",
    	"display a list of all entities - it'll be long",
    	"", -1
    },
    
    {"spec999", G_admin_spec999, "player_control",
      "move 999 pingers to the spectator team",
      "", -1
      },
      
    {"specme", G_admin_putmespec, "self_control",
        "moves you to the spectators",
	"", -1
    },
    
    {"take", G_admin_take, "cheats",
      "take player funds (credits or evos)",
      "[^3name^7] (^5amount^7)", -1
    },
        
    {"time", G_admin_time, "information",
      "show the current local server time",
      "", -1
      },

      {"toggleinstantbuild", G_admin_toggleinstantbuild, "game_control",
    	  "Toggle whether buildings are built instantly or not",
    	  "", -1
      },
      
    {"unban", G_admin_unban, "bans",
      "unbans a player specified by the slot as seen in showbans",
      "[^3ban #^7]", -1
    },
    
    {"unlock", G_admin_unlock, "game_control",
      "unlock a locked team",
      "[^3a|h^7]", -1
    },

    {"undesignate", G_admin_designate, "build",
      "revoke designated builder privileges",
      "[^3name|slot#^7]", -1
    },
    
    {"unmute", G_admin_mute, "player_control",
      "unmute a muted player",
      "[^3name|slot#^7]", -1
    },

    {
     "warn", G_admin_warn, "player_control",
      "Warn a player to cease or face admin intervention",
      "[^3name|slot#^7] [reason]", -1
    },
    
    {
    	"website", G_admin_website, "information",
    	"Show everyone the website address",
    	"", -1
    },
    
    {
    	"xvsall", G_admin_xvsall, "game_control",
    	"Put player X on team Y, and all other players on the other team. Locks the teams that way",
    	"", -1
    }
  };

static int g_admin_number_cmds = sizeof( g_admin_cmds ) / sizeof( g_admin_cmds[ 0 ] );
static int g_admin_number_groups = 0;
static int g_admin_number_levels = 0;
static int g_admin_number_scripts = 0;
static int g_admin_number_bans = 0;
static int g_admin_number_admins = 0;

static int admin_level_maxname = 0;
g_admin_level_t *g_admin_levels[ MAX_ADMIN_LEVELS ];
g_admin_admin_t *g_admin_admins[ MAX_ADMIN_ADMINS ];
g_admin_ban_t *g_admin_bans[ MAX_ADMIN_BANS ];
g_admin_script_t *g_admin_scripts[ MAX_ADMIN_COMMANDS ];
g_admin_namelog_t *g_admin_namelog[ MAX_ADMIN_NAMELOGS ];
g_admin_cmd_group_t* g_admin_cmd_groups[ MAX_ADMIN_CMD_GROUPS ];

qboolean G_admin_flag_permission(gentity_t *ent, char flag) {
	int i;
	int entity_level = 0;
	char *flags;

	//handle the console
	if(!ent && flag == ADMF_NOCENSORFLOOD)
		return qtrue;
	
	for (i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++) {
		if ( !Q_stricmp(ent->client->pers.guid, g_admin_admins[ i ]->guid) ) {
			flags = g_admin_admins[ i ]->flags;
			while ( *flags ) {
				if ( *flags == flag )
					return qtrue;
				else if ( *flags == '-') {
					while ( *flags++) {
						if ( *flags == flag )
							return qfalse;
						else if ( *flags == '+')
							break;
					}
				} else if ( *flags == '*') {
					while ( *flags++) {
						if ( *flags == flag )
							return qfalse;
					}
					// flags with significance only for individuals (
					// like ADMF_INCOGNITO and ADMF_IMMUTABLE are NOT covered 
					// by the '*' wildcard.  They must be specified manually.
					switch (flag ) {
					case ADMF_INCOGNITO:
					case ADMF_IMMUTABLE:
					case ADMF_DBUILDER:
						return qfalse;
					default:
						return qtrue;
					}
				}
				flags++;
			}
			entity_level = g_admin_admins[ i ]->level;
		}
	}
	//get the level of the player in question. Run through the permissions
	//of that level checking for the requested permission
	for (i = 0; i < g_admin_number_levels && g_admin_levels[ i ]; i++) {
		if (g_admin_levels[ i ]->level == entity_level ) {
			flags = g_admin_levels[ i ]->flags;
			while ( *flags ) {
				if ( *flags == flag )
					return qtrue;
				if ( *flags == '*') {
					while ( *flags++) {
						if ( *flags == flag )
							return qfalse;
					}
					// flags with significance only for individuals (
					// like ADMF_INCOGNITO and ADMF_IMMUTABLE are NOT covered 
					// by the '*' wildcard.  They must be specified manually.
					switch (flag ) {
					case ADMF_INCOGNITO:
					case ADMF_IMMUTABLE:
					case ADMF_DBUILDER:
						return qfalse;
					default:
						return qtrue;
					}
				}
				flags++;
			}
		}
	}
	return qfalse;
}

qboolean G_admin_cmd_permission( gentity_t *ent, g_admin_cmd_t* cmd )
{
  int i, entity_level_num;
  g_admin_level_t* entity_level = NULL;

  // console always wins
  if( !ent )
    return qtrue;

  //get the level of this particular entity
  entity_level_num = 0;
  for (i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++) {
  	if ( !Q_stricmp(ent->client->pers.guid, g_admin_admins[ i ]->guid) ) {
  			entity_level_num = g_admin_admins[ i ]->level;
  			i = MAX_ADMIN_ADMINS;
  	}
  }
  for (i = 0; i < g_admin_number_levels; i++) {
	  if( g_admin_levels[ i ]->level == entity_level_num )
		  entity_level = g_admin_levels[ i ];
  }
  
  //now that we have the entity's level, move through that particular level and see
  //if the command requested is allowed
  for (i = 0; i < entity_level->num_allowed_commands; i++) {
	  if(entity_level->allowed_commands[ i ] == cmd) {
		  return qtrue;
	  }	  
  }
  return qfalse;
}

qboolean G_admin_script_permission(gentity_t *ent, g_admin_script_t* script) 
{
	int i, j;
	int entity_level = 0;
	//char *flags;

	// console always wins
	if ( !ent )
		return qtrue;

	//get the level of this particular entity
	for (i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++) {
		if ( !Q_stricmp(ent->client->pers.guid, g_admin_admins[ i ]->guid) ) {
			entity_level = g_admin_admins[ i ]->level;
		}
	}
	//see if this script is allowed by that level
	for (i = 0; i < g_admin_number_levels; i++) {
		if(script->levels[i] == entity_level)
			return qtrue;
	}
	return qfalse;
}

//Check to see if a given name is already taken
qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len )
{
  int i;
  gclient_t *client;
  char testName[ MAX_NAME_LENGTH ] = {""};
  char name2[ MAX_NAME_LENGTH ] = {""};
  int alphaCount = 0;

  G_SanitiseName( name, name2 );

  if( !Q_stricmp( name2, "UnnamedPlayer" ) ) 
    return qtrue;

  if( !Q_stricmp( name2, "console" ) )
  {
    Q_strncpyz( err, va( "The name '%s^7' is invalid here", name2 ),
      len );
    return qfalse;
  }

  for( i = 0; i < level.maxclients; i++ )
  {
    client = &level.clients[ i ];
    if( client->pers.connected != CON_CONNECTING
      && client->pers.connected != CON_CONNECTED ) 
    {
      continue;
    }

    // can rename ones self to the same name using different colors
    if( i == ( ent - g_entities ) )
      continue;

    G_SanitiseName( client->pers.netname, testName );
    if( !Q_stricmp( name2, testName ) )
    {
      Q_strncpyz( err, va( "The name '%s^7' is already in use", name ),
        len );
      return qfalse;
    }
  }
   
  if( '0' <= name2[ 0 ]  && name2[ 0 ] <= '9' )
  {
    Q_strncpyz( err, "Names cannot begin with a number. Please choose another.", len );
    return qfalse;
  }
  
  for( i = 0; name2[ i ] !='\0'; i++)
  {
    if( Q_isalpha( name2[ i ] ) )
     alphaCount++;
    if( name2[ i ] == '/' )
    {
      if( name2[ i + 1 ] == '/' || name2[ i + 1 ] == '*' )
      {
        Q_strncpyz( err, "Names cannot contain '//' or '/*'. Please choose another.", len );
        return qfalse;
      }
    }
  }
  
  if( alphaCount == 0 ) 
  {
    Q_strncpyz( err, va( "The name '%s^7' does not include at least one letter. Please choose another.", name ), len );
    return qfalse;
  }

  if( !g_adminNameProtect.string[ 0 ] )
    return qtrue;

  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
  {
    if( g_admin_admins[ i ]->level < 1 )
      continue;
    G_SanitiseName( g_admin_admins[ i ]->name, testName );
    if( !Q_stricmp( name2, testName ) &&
      Q_stricmp( ent->client->pers.guid, g_admin_admins[ i ]->guid ) )
    {
      Q_strncpyz( err, va( "The name '%s^7' belongs to an admin. "
        "Please choose another.", name ), len );
      return qfalse;
    }
  }
  return qtrue;
}

static qboolean admin_higher_guid( char *admin_guid, char *victim_guid )
{
  int i;
  int alevel = 0;

  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
  {
    if( !Q_stricmp( admin_guid, g_admin_admins[ i ]->guid ) )
    {
      alevel = g_admin_admins[ i ]->level;
      break;
    }
  }
  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
  {
    if( !Q_stricmp( victim_guid, g_admin_admins[ i ]->guid ) )
    {
      if( alevel < g_admin_admins[ i ]->level )
        return qfalse;
      if( strstr( g_admin_admins[ i ]->flags, va( "%c", ADMF_IMMUTABLE ) ) )
        return qfalse;
    }
  }
  return qtrue;
}

static qboolean admin_higher( gentity_t *admin, gentity_t *victim )
{

  // console always wins
  if( !admin )
    return qtrue;
  // just in case
  if( !victim )
    return qtrue;

  return admin_higher_guid( admin->client->pers.guid,
    victim->client->pers.guid );
}

//give an admin level and a group/command name, add all of the commands
//belonging to that group to the list of allowed commands - or just the command,
//for a command. Returns true if the command was added, false if not
static qboolean admin_allow_cmd(g_admin_level_t* admin_level, char* cmdname)
{
	int i;
	g_admin_cmd_group_t* group = NULL;
	
	//find the command group
	for(i = 0; i < g_admin_number_groups; i++) {
		if(!Q_stricmp(g_admin_cmd_groups[i]->groupname, cmdname))
			group = g_admin_cmd_groups[i];
	}
	//check that the group was found. If it wasn't look for a command by that name
	if(group == NULL) {
		for(i = 0; i < g_admin_number_cmds; i++) {
			if(!Q_stricmp(g_admin_cmds[i].keyword, cmdname)) {
				admin_level->allowed_commands[admin_level->num_allowed_commands] = &g_admin_cmds[i];
				admin_level->num_allowed_commands++;
				Com_Printf("Adding command '%s' to level %d\n", cmdname, admin_level->level);
				return qtrue; 
			}
		}
		return qfalse;
	}
	//if the group was found then add the commands to the list
	Com_Printf("Adding command group '%s' (%d commands) to level %d\n", cmdname, group->num_members, admin_level->level);
	for(i = 0; i < group->num_members; i++) {
		admin_level->allowed_commands[ admin_level->num_allowed_commands ] = group->members[ i ];
		admin_level->num_allowed_commands++;
	}
	return qtrue;
}


static void admin_writeconfig_string( char *s, fileHandle_t f )
{
  char buf[ MAX_STRING_CHARS ];

  buf[ 0 ] = '\0';
  if( s[ 0 ] )
  {
    //Q_strcat(buf, sizeof(buf), s);
    Q_strncpyz( buf, s, sizeof( buf ) );
    trap_FS_Write( buf, strlen( buf ), f );
  }
  trap_FS_Write( "\n", 1, f );
}

static void admin_writeconfig_int( int v, fileHandle_t f )
{
  char buf[ 32 ];

  Com_sprintf( buf, sizeof(buf), "%d", v );
  if( buf[ 0 ] )
    trap_FS_Write( buf, strlen( buf ), f );
  trap_FS_Write( "\n", 1, f );
}

/* Given a file handle, f, write out the allowed commands for a given level.
 * It is assumed that the file already has 'allow   = ' written to it,
 * so this simply writes out the list
 */
static void admin_writeconfig_commands( g_admin_level_t* level, fileHandle_t f )
{
	char buf[ MAX_STRING_CHARS ];
	int i, j, read_cmd;
	g_admin_cmd_group_t* cur_group = NULL;
	g_admin_cmd_t* cmd = NULL;
	
	read_cmd = 0; //the number of command in a group we are checking
	
	//run through each command, checking if it is the start of a group or not
	//if it is, and the entire group is there, write the group name. If not
	//write the command
	for(i = 0; i < level->num_allowed_commands; i++) {
		//first check groups. If we aren't currently checking a group, find
		//whatever group this command might fit in
		if(cur_group == NULL) {
			for(j = 0; j < g_admin_number_groups; j++) {
				if(level->allowed_commands[ i ] == g_admin_cmd_groups[ j ]->members[ 0 ]) {
					cur_group = g_admin_cmd_groups[ j ];
					read_cmd = 1;
					j = g_admin_number_groups;
					//Com_Printf("Starting to check group '%s'\n", cur_group->groupname);
				}
			}
			//if we didn't find any groups that this command belongs in, just write
			//the command
			if(cur_group == NULL) {
				trap_FS_Write( va("%s ", level->allowed_commands[ i ]->keyword), Q_PrintStrlen(level->allowed_commands[ i ]->keyword) + 1, f );
				//Com_Printf("Wrote command '%s' to config file\n", level->allowed_commands[ i ]->keyword);
			} else {
				//check if this group only has one member, because if it does we should just write it
				if (cur_group->num_members <= 1) {
					trap_FS_Write(va("%s ", cur_group->groupname), Q_PrintStrlen(cur_group->groupname) + 1, f );
					//Com_Printf( "Wrote group '%s' to config file because it only had one member\n", cur_group->groupname);
					//now clear out the group matching stuff, since we are done with this group
					cur_group = NULL;
					read_cmd = 0;
				}
			}
		} else {
			//we are currently checking a group, so check the next command
			if(level->allowed_commands[ i ] == cur_group->members[ read_cmd ]) {
				//Com_Printf(".");
				read_cmd++;
				//check if this is the last command in the group
				if(read_cmd == cur_group->num_members) {
					//if it is, write the whole group out
					trap_FS_Write( va("%s ", cur_group->groupname), Q_PrintStrlen(cur_group->groupname) + 1, f );
					//Com_Printf("Wrote group '%s' to config file\n", cur_group->groupname);
					//now clear out the group matching stuff, since we are done with this group
					cur_group = NULL;
					read_cmd = 0;
				} 
			}
			//otherwise, we have matched several commands in the group, but not the entire group
			else {
				//write all the commands out that we've encountered so far
				for(j = 0; j < read_cmd; j++) {
					cmd = cur_group->members[ j ];
					trap_FS_Write( va("%s ", cmd->keyword), Q_PrintStrlen(cmd->keyword) + 1, f );
					//Com_Printf("Wrote command '%s' that was part of a group to config file\n", cmd->keyword);
				}
				//reset the group checking state
				read_cmd = 0;
				cur_group = NULL;
			}
		}	
	}
	trap_FS_Write( "\n", 1, f );
}

static void admin_writeconfig( void )
{
  fileHandle_t f;
  int len, i, j;
  qtime_t qt;
  int t;
  char levels[ MAX_STRING_CHARS ] = {""};

  if( !g_admin.string[ 0 ] )
  {
    G_Printf( S_COLOR_YELLOW "WARNING: g_admin is not set. "
      " configuration will not be saved to a file.\n" );
    return;
  }
  t = trap_RealTime( &qt );
  len = trap_FS_FOpenFile( g_admin.string, &f, FS_WRITE );
  if( len < 0 )
  {
    G_Printf( "admin_writeconfig: could not open g_admin file \"%s\"\n",
              g_admin.string );
    return;
  }
  for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
  {
    trap_FS_Write( "[level]\n", 8, f );
    trap_FS_Write( "level   = ", 10, f );
    admin_writeconfig_int( g_admin_levels[ i ]->level, f );
    trap_FS_Write( "name    = ", 10, f );
    admin_writeconfig_string( g_admin_levels[ i ]->name, f );
    trap_FS_Write( "flags   = ", 10, f );
    admin_writeconfig_string( g_admin_levels[ i ]->flags, f );
    trap_FS_Write( "allow   = ", 10, f );
    admin_writeconfig_commands( g_admin_levels[ i ], f );
    trap_FS_Write( "\n", 1, f );
  }
  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
  {
    // don't write level 0 users
    if( g_admin_admins[ i ]->level < 1 )
      continue;

    trap_FS_Write( "[admin]\n", 8, f );
    trap_FS_Write( "name    = ", 10, f );
    admin_writeconfig_string( g_admin_admins[ i ]->name, f );
    trap_FS_Write( "guid    = ", 10, f );
    admin_writeconfig_string( g_admin_admins[ i ]->guid, f );
    trap_FS_Write( "level   = ", 10, f );
    admin_writeconfig_int( g_admin_admins[ i ]->level, f );
    trap_FS_Write( "flags   = ", 10, f );
    admin_writeconfig_string( g_admin_admins[ i ]->flags, f );
    trap_FS_Write( "\n", 1, f );
  }
  for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
  {
    // don't write expired bans
    // if expires is 0, then it's a perm ban
    if( g_admin_bans[ i ]->expires != 0 &&
         ( g_admin_bans[ i ]->expires - t ) < 1 )
      continue;

    trap_FS_Write( "[ban]\n", 6, f );
    trap_FS_Write( "name    = ", 10, f );
    admin_writeconfig_string( g_admin_bans[ i ]->name, f );
    trap_FS_Write( "guid    = ", 10, f );
    admin_writeconfig_string( g_admin_bans[ i ]->guid, f );
    trap_FS_Write( "ip      = ", 10, f );
    admin_writeconfig_string( g_admin_bans[ i ]->ip, f );
    trap_FS_Write( "reason  = ", 10, f );
    admin_writeconfig_string( g_admin_bans[ i ]->reason, f );
    trap_FS_Write( "made    = ", 10, f );
    admin_writeconfig_string( g_admin_bans[ i ]->made, f );
    trap_FS_Write( "expires = ", 10, f );
    admin_writeconfig_int( g_admin_bans[ i ]->expires, f );
    trap_FS_Write( "banner  = ", 10, f );
    admin_writeconfig_string( g_admin_bans[ i ]->banner, f );
    trap_FS_Write( "\n", 1, f );
  }
  for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_scripts[ i ]; i++ )
  {
    levels[ 0 ] = '\0';
    trap_FS_Write( "[script]\n", 10, f );
    trap_FS_Write( "command_name = ", 10, f );
    admin_writeconfig_string( g_admin_scripts[ i ]->command_name, f );
    trap_FS_Write( "exec    = ", 10, f );
    admin_writeconfig_string( g_admin_scripts[ i ]->exec, f );
    trap_FS_Write( "desc    = ", 10, f );
    admin_writeconfig_string( g_admin_scripts[ i ]->desc, f );
    trap_FS_Write( "levels  = ", 10, f );
    for( j = 0; g_admin_scripts[ i ]->levels[ j ] != -1; j++ )
    {
      Q_strcat( levels, sizeof( levels ),
                va( "%i ", g_admin_scripts[ i ]->levels[ j ] ) );
    }
    admin_writeconfig_string( levels, f );
    trap_FS_Write( "\n", 1, f );
  }
  trap_FS_FCloseFile( f );
}

static void admin_readconfig_string( char **cnf, char *s, int size )
{
  char * t;

  //COM_MatchToken(cnf, "=");
  t = COM_ParseExt( cnf, qfalse );
  if( !strcmp( t, "=" ) )
  {
    t = COM_ParseExt( cnf, qfalse );
  }
  else
  {
    G_Printf( "readconfig: warning missing = before "
              "\"%s\" on line %d\n",
              t,
              COM_GetCurrentParseLine() );
  }
  s[ 0 ] = '\0';
  while( t[ 0 ] )
  {
    if( ( s[ 0 ] == '\0' && strlen( t ) <= size ) 
      || ( strlen( t ) + strlen( s ) < size ) )
    {

      Q_strcat( s, size, t );
      Q_strcat( s, size, " " );
    }
    t = COM_ParseExt( cnf, qfalse );
  }
  // trim the trailing space
  if( strlen( s ) > 0 && s[ strlen( s ) - 1 ] == ' ' )
    s[ strlen( s ) - 1 ] = '\0';
}

static void admin_readconfig_int( char **cnf, int *v )
{
  char * t;

  //COM_MatchToken(cnf, "=");
  t = COM_ParseExt( cnf, qfalse );
  if( !strcmp( t, "=" ) )
  {
    t = COM_ParseExt( cnf, qfalse );
  }
  else
  {
    G_Printf( "readconfig: warning missing = before "
              "\"%s\" on line %d\n",
              t,
              COM_GetCurrentParseLine() );
  }
  *v = atoi( t );
}

//Given an admin level structure and a string of space-separated commands,
//give the admin level permission to use those commands. Temp must already be
//null-terminated before calling this function. The Temp list should start with
//a command, not a space
static void admin_readconfig_parsecommands( g_admin_level_t* level, char* temp )
{
	char cur_command_str[MAX_ADMIN_CMD_LEN];
	int read_index = 0;
	int write_index = 0;
	
	//Com_Printf("Adding the following command string to admin level %d:\n'%s'\n", level->level, temp);
	//while we are not yet done processing all of the temp string
	while(temp[read_index]) {
		//while we are reading in a command, copy it to our temp buffer
		while(temp[read_index] && temp[read_index] != ' ') {
			cur_command_str[write_index++] = temp[read_index++];
		}
		//by now, we are reading from a space and we have written a whole temp string, minus the terminating
		//null character
		cur_command_str[write_index] = '\0';
		admin_allow_cmd( level, cur_command_str );
		//reset the temp writing string
		//jump to the next character if we have more to read so that we can read the next word
		if(temp[read_index])
			read_index++;
		write_index = 0;
	}
}


//set up the g_admin_cmd_groups array and all of the g_admin_cmd
//structures properly. This should only be called once, by g_main::G_InitGame
qboolean G_admin_setup_cmd_groups( void )
{
	int i, j, len;
	g_admin_cmd_group_t *cur_group = NULL;
	g_admin_number_groups = 0;
	
	Com_Printf("Setting up command groups for %d commands\n", g_admin_number_cmds);
	
	//first move through all of the commands, setting up their command number
	//and getting their group
	for(i = 0; i < g_admin_number_cmds; i++) {
		//clear out the current group from the last loop
		cur_group = NULL;
		
		g_admin_cmds[i].command_number = i;
		len = Q_PrintStrlen( g_admin_cmds[i].group ) + 1;
		//check to make sure we don't already have this group in our list
		for(j = 0; j < g_admin_number_groups; j++) {
			if( !Q_stricmp( g_admin_cmds[ i ].group, g_admin_cmd_groups[ j ]->groupname ) )
				cur_group = g_admin_cmd_groups[ j ];
		}
		//if we didn't already find a group with that name, it must be a new group
		if(cur_group == NULL) {
			cur_group = G_Alloc(sizeof(g_admin_cmd_group_t) * sizeof(char*));
			*(cur_group->members) = &g_admin_cmds[i];
			cur_group->num_members = 1;

			
			*(cur_group->groupname) = '\0';
			cur_group->groupname = G_Alloc( len * sizeof(char) );			
			Q_strncpyz( cur_group->groupname, g_admin_cmds[i].group, len * sizeof(char) );
			g_admin_cmd_groups[ g_admin_number_groups ] = cur_group;
			g_admin_number_groups++;
		}
		//otherwise, just add the new command
		else {
			cur_group->members[ cur_group->num_members ] = &g_admin_cmds[i];
			cur_group->num_members++;
		}
	}
	//show the groups that were set up
	for(i = 0; i < g_admin_number_groups; i++) {
		cur_group = g_admin_cmd_groups[ i ];
		Com_Printf("%s:\n", cur_group->groupname);
		for(j = 0; j < cur_group->num_members; j++)
			Com_Printf("  %s\n", cur_group->members[ j ]->keyword);
	}
	Com_Printf("Command group setup complete\n");
	return qtrue;
}


// if we can't parse any levels from readconfig, set up default
// ones to make new installs easier for admins
static void admin_default_levels( void )
{
  g_admin_level_t* l;
  int i;
  
  for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
  {
    G_Free( g_admin_levels[ i ] );
    g_admin_levels[ i ] = NULL;
  }
  for( i = 0; i <= 5; i++ )
  {
    l = G_Alloc( sizeof( g_admin_level_t ) );
    l->level = i;
    *l->name = '\0';
    *l->flags = '\0';
    l->num_allowed_commands = 0;
    g_admin_levels[ i ] = l;
  }
  
  Q_strncpyz( g_admin_levels[ 0 ]->name, "^4Unknown Player", sizeof( l->name ) );
  admin_allow_cmd(g_admin_levels[ 0 ], "information");
  
  Q_strncpyz( g_admin_levels[ 1 ]->name, "^5Server Regular", sizeof( l->name ) );
  admin_allow_cmd(g_admin_levels[ 1 ], "information");
  admin_allow_cmd(g_admin_levels[ 1 ], "self_control");
      
  Q_strncpyz( g_admin_levels[ 2 ]->name, "^6Team Manager", sizeof( l->name ) );
  admin_allow_cmd(g_admin_levels[ 2 ], "information");
  admin_allow_cmd(g_admin_levels[ 2 ], "self_control");
  admin_allow_cmd(g_admin_levels[ 2 ], "build");
    
  Q_strncpyz( g_admin_levels[ 3 ]->name, "^2Junior Admin", sizeof( l->name ) );
  admin_allow_cmd(g_admin_levels[ 3 ], "information");
  admin_allow_cmd(g_admin_levels[ 3 ], "self_control");
  admin_allow_cmd(g_admin_levels[ 3 ], "build");
  admin_allow_cmd(g_admin_levels[ 3 ], "player_control");

  Q_strncpyz( g_admin_levels[ 4 ]->name, "^3Senior Admin", sizeof( l->name ) );
  admin_allow_cmd(g_admin_levels[ 4 ], "information");
  admin_allow_cmd(g_admin_levels[ 4 ], "self_control");
  admin_allow_cmd(g_admin_levels[ 4 ], "build");
  admin_allow_cmd(g_admin_levels[ 4 ], "player_control");
  admin_allow_cmd(g_admin_levels[ 4 ], "game_control");


  Q_strncpyz( g_admin_levels[ 5 ]->name, "^1Server Operator", sizeof( l->name ) );
  admin_allow_cmd(g_admin_levels[ 5 ], "information");
  admin_allow_cmd(g_admin_levels[ 5 ], "self_control");
  admin_allow_cmd(g_admin_levels[ 5 ], "build");
  admin_allow_cmd(g_admin_levels[ 5 ], "player_control");
  admin_allow_cmd(g_admin_levels[ 5 ], "game_control");
  admin_allow_cmd(g_admin_levels[ 5 ], "server_control");
}

//  return a level for a player entity.
int G_admin_level( gentity_t *ent )
{
  int i;
  qboolean found = qfalse;

  if( !ent )
  {
    return MAX_ADMIN_LEVELS;
  }

  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
  {
    if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
    {

      found = qtrue;
      break;
    }
  }

  if( found )
  {
    return g_admin_admins[ i ]->level;
  }

  return 0;
}

static qboolean admin_script_permission( gentity_t *ent, char *command )
{
  int i, j;
  int level;

  if( !ent )
    return qtrue;
  level  = ent->client->pers.adminLevel;
  //run through all of the scripts
  for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_scripts[ i ]; i++ )
  {
	  //if the command is actually a script
    if( !Q_stricmp( command, g_admin_scripts[ i ]->command_name ) )
    {
    	//run through what level admin can run the script
      for( j = 0; g_admin_scripts[ i ]->levels[ j ] != -1; j++ )
      {
    	  //if the current level is found in the list of levels, return true
        if( g_admin_scripts[ i ]->levels[ j ] == level )
        {
          return qtrue;
        }
      }
    }
  }
  return qfalse;
}

static void admin_log( gentity_t *admin, char *cmd, int skiparg )
{
  fileHandle_t f;
  int len, i, j;
  char string[ MAX_STRING_CHARS ];
  int min, tens, sec;
  g_admin_admin_t *a;
  g_admin_level_t *l;
  char flags[ MAX_ADMIN_FLAGS * 2 ];
  gentity_t *victim = NULL;
  int pids[ MAX_CLIENTS ];
  char name[ MAX_NAME_LENGTH ];

  if( !g_adminLog.string[ 0 ] )
    return ;


  len = trap_FS_FOpenFile( g_adminLog.string, &f, FS_APPEND );
  if( len < 0 )
  {
    G_Printf( "admin_log: error could not open %s\n", g_adminLog.string );
    return ;
  }

  sec = level.time / 1000;
  min = sec / 60;
  sec -= min * 60;
  tens = sec / 10;
  sec -= tens * 10;

  *flags = '\0';
  if( admin )
  {
    for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
    {
      if( !Q_stricmp( g_admin_admins[ i ]->guid , admin->client->pers.guid ) )
      {

        a = g_admin_admins[ i ];
        Q_strncpyz( flags, a->flags, sizeof( flags ) );
        for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
        {
          if( g_admin_levels[ j ]->level == a->level )
          {
            l = g_admin_levels[ j ];
            //Q_strcat( flags, sizeof( flags ), l->allowed_commands );
            break;
          }
        }
        break;
      }
    }
  }

  if( G_SayArgc() > 1 + skiparg )
  {
    G_SayArgv( 1 + skiparg, name, sizeof( name ) );
    if( G_ClientNumbersFromString( name, pids ) == 1 )
    {
      victim = &g_entities[ pids[ 0 ] ];
    }
  }

  if( victim && Q_stricmp( cmd, "attempted" ) )
  {
    Com_sprintf( string, sizeof( string ),
                 "%3i:%i%i: %i: %s: %s: %s: %s: %s: %s: \"%s\"\n",
                 min,
                 tens,
                 sec,
                 ( admin ) ? admin->s.clientNum : -1,
                 ( admin ) ? admin->client->pers.guid
                 : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
                 ( admin ) ? admin->client->pers.netname : "console",
                 flags,
                 cmd,
                 victim->client->pers.guid,
                 victim->client->pers.netname,
                 G_SayConcatArgs( 2 + skiparg ) );
  }
  else
  {
    Com_sprintf( string, sizeof( string ),
                 "%3i:%i%i: %i: %s: %s: %s: %s: \"%s\"\n",
                 min,
                 tens,
                 sec,
                 ( admin ) ? admin->s.clientNum : -1,
                 ( admin ) ? admin->client->pers.guid
                 : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
                 ( admin ) ? admin->client->pers.netname : "console",
                 flags,
                 cmd,
                 G_SayConcatArgs( 1 + skiparg ) );
  }
  trap_FS_Write( string, strlen( string ), f );
  trap_FS_FCloseFile( f );
}

static int admin_listadmins( gentity_t *ent, int start, char *search, int minlevel )
{
  int drawn = 0;
  char guid_stub[9];
  char name[ MAX_NAME_LENGTH ] = {""};
  char name2[ MAX_NAME_LENGTH ] = {""};
  char lname[ MAX_NAME_LENGTH ] = {""};
  char lname_fmt[ 5 ];
  int i,j;
  gentity_t *vic;
  int l = 0;
  qboolean dup = qfalse;
  
  ADMBP_begin();

  // print out all connected players regardless of level if name searching
  for( i = 0; i < level.maxclients && search[ 0 ]; i++ )
  {
    vic = &g_entities[ i ];

    if( vic->client && vic->client->pers.connected != CON_CONNECTED )
      continue;
    
    l = vic->client->pers.adminLevel;

    G_SanitiseName( vic->client->pers.netname, name );
    if( !strstr( name, search ) )
      continue;

    for( j = 0; j <= 8; j++ )
      guid_stub[ j ] = vic->client->pers.guid[ j + 24 ];
    guid_stub[ j ] = '\0';
  
    lname[ 0 ] = '\0'; 
    Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) );
    for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
    {
      if( g_admin_levels[ j ]->level == l )
      {
        G_DecolorString( g_admin_levels[ j ]->name, lname );
        Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is",
          ( admin_level_maxname + strlen( g_admin_levels[ j ]->name )
            - strlen( lname ) ) );
        Com_sprintf( lname, sizeof( lname ), lname_fmt,
           g_admin_levels[ j ]->name );
        break;
      }
    }
    ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n",
      i,
      l,
      lname,
      guid_stub,
      vic->client->pers.netname ) );
    drawn++;
  }

  for( i = start; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]
    && drawn < MAX_ADMIN_LISTITEMS; i++ )
   if( g_admin_admins[ i ]->level >= minlevel ) 
   {
     if( search[ 0 ] )
     {
       G_SanitiseName( g_admin_admins[ i ]->name, name );
       if( !strstr( name, search ) )
         continue;
      
       // verify we don't have the same guid/name pair in connected players
       // since we don't want to draw the same player twice
       dup = qfalse;
       for( j = 0; j < level.maxclients; j++ )
       {
         vic = &g_entities[ j ];
         if( !vic->client || vic->client->pers.connected != CON_CONNECTED )
           continue;
         G_SanitiseName( vic->client->pers.netname, name2 );
         if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid )
           && strstr( name2, search ) ) 
         {
           dup = qtrue;
           break;
         }
       }
       if( dup )
         continue;
     }
     for( j = 0; j <= 8; j++ )
       guid_stub[ j ] = g_admin_admins[ i ]->guid[ j + 24 ];
     guid_stub[ j ] = '\0';
    
     lname[ 0 ] = '\0'; 
     Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) );
     for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
     {
       if( g_admin_levels[ j ]->level == g_admin_admins[ i ]->level )
       {
         G_DecolorString( g_admin_levels[ j ]->name, lname );
         Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is",
           ( admin_level_maxname + strlen( g_admin_levels[ j ]->name )
             - strlen( lname ) ) );
         Com_sprintf( lname, sizeof( lname ), lname_fmt,
            g_admin_levels[ j ]->name );
         break;
       }
     }
     ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n",
       ( i + MAX_CLIENTS ),
       g_admin_admins[ i ]->level,
       lname,
       guid_stub,
       g_admin_admins[ i ]->name ) );
     drawn++;
   }
  ADMBP_end();
  return drawn;
}

void G_admin_duration( int secs, char *duration, int dursize )
{

  if( secs > ( 60 * 60 * 24 * 365 * 50 ) || secs < 0 )
    Q_strncpyz( duration, "PERMANENT", dursize );
  else if( secs >= ( 60 * 60 * 24 * 365 ) )
    Com_sprintf( duration, dursize, "%1.1f years",
      ( secs / ( 60 * 60 * 24 * 365.0f ) ) );
  else if( secs >= ( 60 * 60 * 24 * 90 ) )
    Com_sprintf( duration, dursize, "%1.1f weeks",
      ( secs / ( 60 * 60 * 24 * 7.0f ) ) );
  else if( secs >= ( 60 * 60 * 24 ) )
    Com_sprintf( duration, dursize, "%1.1f days",
      ( secs / ( 60 * 60 * 24.0f ) ) );
  else if( secs >= ( 60 * 60 ) )
    Com_sprintf( duration, dursize, "%1.1f hours",
      ( secs / ( 60 * 60.0f ) ) );
  else if( secs >= 60 )
    Com_sprintf( duration, dursize, "%1.1f minutes",
      ( secs / 60.0f ) );
  else
    Com_sprintf( duration, dursize, "%i seconds", secs );
}

qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen )
{
  char *guid, *ip;
  int i;
  qtime_t qt;
  int t;
 
  *reason = '\0'; 
  t  = trap_RealTime( &qt ); 
  if( !*userinfo )
    return qfalse;
  ip = Info_ValueForKey( userinfo, "ip" );
  if( !*ip )
    return qfalse;
  guid = Info_ValueForKey( userinfo, "cl_guid" );
  for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
  {
    // 0 is for perm ban
    if( g_admin_bans[ i ]->expires != 0 &&
         ( g_admin_bans[ i ]->expires - t ) < 1 )
      continue;
    if( strstr( ip, g_admin_bans[ i ]->ip ) )
    {
      char duration[ 32 ];
      G_admin_duration( ( g_admin_bans[ i ]->expires - t ),
        duration, sizeof( duration ) );
      Com_sprintf(
        reason,
        rlen,
        "You have been banned by %s^7 reason: %s^7 expires: %s",
        g_admin_bans[ i ]->banner,
        g_admin_bans[ i ]->reason,
        duration
      );
      G_Printf("Banned player tried to connect from IP %s\n", ip);
      return qtrue;
    }
    if( *guid && !Q_stricmp( g_admin_bans[ i ]->guid, guid ) )
    {
      char duration[ 32 ];
      G_admin_duration( ( g_admin_bans[ i ]->expires - t ),
        duration, sizeof( duration ) );
      Com_sprintf(
        reason,
        rlen,
        "You have been banned by %s^7 reason: %s^7 expires: %s",
        g_admin_bans[ i ]->banner,
        g_admin_bans[ i ]->reason,
        duration
      );
      G_Printf("Banned player tried to connect with GUID %s\n", guid);
      return qtrue;
    }
  }
  return qfalse;
}

qboolean G_admin_cmd_check( gentity_t *ent, qboolean say )
{
  int i;
  char command[ MAX_ADMIN_CMD_LEN ];
  char *cmd;
  int skip = 0;

  command[ 0 ] = '\0';
  G_SayArgv( 0, command, sizeof( command ) );
  if( !Q_stricmp( command, "say" ) ||
       ( G_admin_flag_permission( ent, ADMF_TEAMCHAT_CMD ) &&
         ( !Q_stricmp( command, "say_team" ) ) ) )
  {
    skip = 1;
    G_SayArgv( 1, command, sizeof( command ) );
  }
  if( !command[ 0 ] ) {
	  return qfalse;
  }
  
  if( command[ 0 ] == '!' )
  {
    cmd = &command[ 1 ];
  }
  else
  {
	return qfalse;
  }
  
  // Flood limit.  If they're talking too fast, determine that and return.
  if( g_floodMinTime.integer )
   if ( G_Flood_Limited( ent ) )
   {
	trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" );
    return qtrue;
   }

  //check to see if the command is a script.
  for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_scripts[ i ]; i++ )
  {
    if( Q_stricmp( cmd, g_admin_scripts[ i ]->command_name ) )
      continue;

    if( G_admin_script_permission( ent, g_admin_scripts[ i ] ) )
    {
      trap_SendConsoleCommand( EXEC_APPEND, g_admin_scripts[ i ]->exec );
      admin_log( ent, cmd, skip );
    }
    else
    {
      ADMP( va( "^3!%s: ^7you do not have permission to use this script\n", g_admin_scripts[ i ]->command_name ) );
      admin_log( ent, "attempted", skip - 1 );
    }
    return qtrue;
  }
  //now check to see if the command is a built-in command
  for( i = 0; i < g_admin_number_cmds; i++ )
  {
    if( Q_stricmp( cmd, g_admin_cmds[ i ].keyword ) )
      continue;

    if( G_admin_cmd_permission( ent, &g_admin_cmds[ i ] ) )
    {
      g_admin_cmds[ i ].handler( ent, skip );
      admin_log( ent, cmd, skip );
    }
    else
    {
      ADMP( va( "^3!%s: ^7you do not have permission to use this command\n", g_admin_cmds[ i ].keyword ) );
      admin_log( ent, "attempted", skip - 1 );
    }
    return qtrue;
  }
  return qfalse;
}

void G_admin_namelog_cleanup( )
{
  int i;

  for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
  {
    G_Free( g_admin_namelog[ i ] );
    g_admin_namelog[ i ] = NULL;
  }
}

void G_admin_namelog_update( gclient_t *client, qboolean disconnect )
{
  int i, j;
  g_admin_namelog_t *namelog;
  char n1[ MAX_NAME_LENGTH ];
  char n2[ MAX_NAME_LENGTH ];
  int clientNum = ( client - level.clients );

  G_SanitiseName( client->pers.netname, n1 );
  for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
  {
    if( disconnect && g_admin_namelog[ i ]->slot != clientNum )
      continue;

    if( !disconnect && !( g_admin_namelog[ i ]->slot == clientNum ||
                          g_admin_namelog[ i ]->slot == -1 ) )
    {
      continue;
    }

    if( !Q_stricmp( client->pers.ip, g_admin_namelog[ i ]->ip )
      && !Q_stricmp( client->pers.guid, g_admin_namelog[ i ]->guid ) )
    {
      for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES
        && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
      {
        G_SanitiseName( g_admin_namelog[ i ]->name[ j ], n2 );
        if( !Q_stricmp( n1, n2 ) ) 
          break;
      }
      if( j == MAX_ADMIN_NAMELOG_NAMES )
        j = MAX_ADMIN_NAMELOG_NAMES - 1;
      Q_strncpyz( g_admin_namelog[ i ]->name[ j ], client->pers.netname,
        sizeof( g_admin_namelog[ i ]->name[ j ] ) );
      g_admin_namelog[ i ]->slot = ( disconnect ) ? -1 : clientNum;
 
      // if this player is connecting, they are no longer banned
      if( !disconnect )
        g_admin_namelog[ i ]->banned = qfalse;

      return;
    }
  }
  if( i >= MAX_ADMIN_NAMELOGS )
  {
    G_Printf( "G_admin_namelog_update: warning, g_admin_namelogs overflow\n" );
    return;
  }
  namelog = G_Alloc( sizeof( g_admin_namelog_t ) );
  memset( namelog, 0, sizeof( namelog ) );
  for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES ; j++ )
    namelog->name[ j ][ 0 ] = '\0';
  Q_strncpyz( namelog->ip, client->pers.ip, sizeof( namelog->ip ) );
  Q_strncpyz( namelog->guid, client->pers.guid, sizeof( namelog->guid ) );
  Q_strncpyz( namelog->name[ 0 ], client->pers.netname,
    sizeof( namelog->name[ 0 ] ) );
  namelog->slot = ( disconnect ) ? -1 : clientNum;
  g_admin_namelog[ i ] = namelog;
}

qboolean G_admin_readconfig( gentity_t *ent, int skiparg )
{
  g_admin_level_t *admin_levels = NULL;
  g_admin_admin_t *admin_admins = NULL;
  g_admin_ban_t *admin_bans = NULL;
  g_admin_script_t *admin_scripts = NULL;
  int i = 0;
  fileHandle_t f;
  int len;
  char *file_contents, *file_contents2;
  char *t;
  qboolean level_open, admin_open, ban_open, script_open;
  char levels[ MAX_STRING_CHARS ] = {""};
  char temp[MAX_ADMIN_COMMANDS * MAX_ADMIN_CMD_LEN];
      	  
  G_admin_cleanup();

  if( !g_admin.string[ 0 ] )
  {
    ADMP( "^3!readconfig: g_admin is not set, not loading configuration "
      "from admin_admins file\n" );
    admin_default_levels();
    return qfalse;
  }

  len = trap_FS_FOpenFile( g_admin.string, &f, FS_READ ) ;
  if( len < 0 )
  {
    ADMP( va( "^3!readconfig: ^7could not open admin config file %s\n",
            g_admin.string ) );
    admin_default_levels();
    return qfalse;
  }
  file_contents = G_Alloc( len + 1 );
  file_contents2 = file_contents;
  trap_FS_Read( file_contents, len, f );
  *( file_contents + len ) = '\0';
  trap_FS_FCloseFile( f );

  t = COM_Parse( &file_contents );
  level_open = admin_open = ban_open = script_open = qfalse;
  while( *t )
  {
    if( !Q_stricmp( t, "[level]" ) ||
         !Q_stricmp( t, "[admin]" ) ||
         !Q_stricmp( t, "[ban]" ) ||
         !Q_stricmp( t, "[script]" ) )
    {

      if( level_open )
        g_admin_levels[ g_admin_number_levels++ ] = admin_levels;
      else if( admin_open )
        g_admin_admins[ g_admin_number_admins++ ] = admin_admins;
      else if( ban_open )
        g_admin_bans[ g_admin_number_bans++ ] = admin_bans;
      else if( script_open )
        g_admin_scripts[ g_admin_number_scripts++ ] = admin_scripts;
      level_open = admin_open =
                     ban_open = script_open = qfalse;
    }

    if( level_open )
    {
      if( !Q_stricmp( t, "level" ) )
      {
        admin_readconfig_int( &file_contents, &admin_levels->level );
      }
      else if( !Q_stricmp( t, "name" ) )
      {
        admin_readconfig_string( &file_contents, admin_levels->name, sizeof( admin_levels->name ) );
      }
      else if( !Q_stricmp( t, "flags" ) )
      {
        admin_readconfig_string( &file_contents, admin_levels->flags, sizeof( admin_levels->flags ) );
      }
      else if( !Q_stricmp( t, "allow" ) )
      {
    	  admin_readconfig_string( &file_contents, temp, sizeof( temp ) );
    	  //now parse the line of commands into actual commands
    	  admin_readconfig_parsecommands( admin_levels, temp );
      }
      else
      {
        ADMP( va( "^3!readconfig: ^7[level] parse error near %s on line %d\n",
                t,
                COM_GetCurrentParseLine() ) );
      }
    }
    else if( admin_open )
    {
      if( !Q_stricmp( t, "name" ) )
      {
        admin_readconfig_string( &file_contents, admin_admins->name, sizeof( admin_admins->name ) );
      }
      else if( !Q_stricmp( t, "guid" ) )
      {
        admin_readconfig_string( &file_contents, admin_admins->guid, sizeof( admin_admins->guid ) );
      }
      else if( !Q_stricmp( t, "level" ) )
      {
        admin_readconfig_int( &file_contents, &admin_admins->level );
      }
      else if( !Q_stricmp( t, "flags" ) )
      {
        admin_readconfig_string( &file_contents, admin_admins->flags, sizeof( admin_admins->flags ) );
      }
      else
      {
        ADMP( va( "^3!readconfig: ^7[admin] parse error near %s on line %d\n",
                t,
                COM_GetCurrentParseLine() ) );
      }

    }
    else if( ban_open )
    {
      if( !Q_stricmp( t, "name" ) )
      {
        admin_readconfig_string( &file_contents, admin_bans->name, sizeof( admin_bans->name ) );
      }
      else if( !Q_stricmp( t, "guid" ) )
      {
        admin_readconfig_string( &file_contents, admin_bans->guid, sizeof( admin_bans->guid ) );
      }
      else if( !Q_stricmp( t, "ip" ) )
      {
        admin_readconfig_string( &file_contents, admin_bans->ip, sizeof( admin_bans->ip ) );
      }
      else if( !Q_stricmp( t, "reason" ) )
      {
        admin_readconfig_string( &file_contents, admin_bans->reason, sizeof( admin_bans->reason ) );
      }
      else if( !Q_stricmp( t, "made" ) )
      {
        admin_readconfig_string( &file_contents, admin_bans->made, sizeof( admin_bans->made ) );
      }
      else if( !Q_stricmp( t, "expires" ) )
      {
        admin_readconfig_int( &file_contents, &admin_bans->expires );
      }
      else if( !Q_stricmp( t, "banner" ) )
      {
        admin_readconfig_string( &file_contents, admin_bans->banner, sizeof( admin_bans->banner ) );
      }
      else
      {
        ADMP( va( "^3!readconfig: ^7[ban] parse error near %s on line %d\n",
                t,
                COM_GetCurrentParseLine() ) );
      }
    }
    else if( script_open )
    {
      if( !Q_stricmp( t, "command" ) )
      {
        admin_readconfig_string( &file_contents, admin_scripts->command_name, sizeof( admin_scripts->command_name ) );
      }
      else if( !Q_stricmp( t, "exec" ) )
      {
        admin_readconfig_string( &file_contents, admin_scripts->exec, sizeof( admin_scripts->exec ) );
      }
      else if( !Q_stricmp( t, "desc" ) )
      {
        admin_readconfig_string( &file_contents, admin_scripts->desc, sizeof( admin_scripts->desc ) );
      }
      else if( !Q_stricmp( t, "levels" ) )
      {
        char level[ 4 ] = {""};
        char *lp = levels;
        int cmdlevel = 0;

        admin_readconfig_string( &file_contents, levels, sizeof( levels ) );
        while( *lp )
        {
          if( *lp == ' ' )
          {
            admin_scripts->levels[ cmdlevel++ ] = atoi( level );
            level[ 0 ] = '\0';
            lp++;
            continue;
          }
          Q_strcat( level, sizeof( level ), va( "%c", *lp ) );
          lp++;
        }
        if( level[ 0 ] )
          admin_scripts->levels[ cmdlevel++ ] = atoi( level );
        // ensure the list is -1 terminated
        admin_scripts->levels[ MAX_ADMIN_LEVELS ] = -1;
      }
      else
      {
        ADMP( va( "^3!readconfig: ^7[script] parse error near %s on line %d\n",
                t,
                COM_GetCurrentParseLine() ) );
      }
    }

    if( !Q_stricmp( t, "[level]" ) )
    {
      if( g_admin_number_levels >= MAX_ADMIN_LEVELS )
        return qfalse;
      admin_levels = G_Alloc( sizeof( g_admin_level_t ) );
      admin_levels->level = 0;
      *admin_levels->name = '\0';
      *admin_levels->allowed_commands = '\0';
      level_open = qtrue;
    }
    else if( !Q_stricmp( t, "[admin]" ) )
    {
      if( g_admin_number_admins >= MAX_ADMIN_ADMINS )
        return qfalse;
      admin_admins = G_Alloc( sizeof( g_admin_admin_t ) );
      *admin_admins->name = '\0';
      *admin_admins->guid = '\0';
      admin_admins->level = 0;
      *admin_admins->flags = '\0';
      admin_open = qtrue;
    }
    else if( !Q_stricmp( t, "[ban]" ) )
    {
      if( g_admin_number_bans >= MAX_ADMIN_BANS )
        return qfalse;
      admin_bans = G_Alloc( sizeof( g_admin_ban_t ) );
      *admin_bans->name = '\0';
      *admin_bans->guid = '\0';
      *admin_bans->ip = '\0';
      *admin_bans->made = '\0';
      admin_bans->expires = 0;
      *admin_bans->reason = '\0';
      ban_open = qtrue;
    }
    else if( !Q_stricmp( t, "[script]" ) )
    {
      if( g_admin_number_scripts >= MAX_ADMIN_COMMANDS )
        return qfalse;
      admin_scripts = G_Alloc( sizeof( g_admin_script_t ) );
      *admin_scripts->command_name = '\0';
      *admin_scripts->exec = '\0';
      *admin_scripts->desc = '\0';
      memset( admin_scripts->levels, -1, sizeof( admin_scripts->levels ) );
      script_open = qtrue;
    }
    t = COM_Parse( &file_contents );
  }
  if( level_open )
  {

    g_admin_levels[ g_admin_number_levels++ ] = admin_levels;
  }
  if( admin_open )
    g_admin_admins[ g_admin_number_admins++ ] = admin_admins;
  if( ban_open )
    g_admin_bans[ g_admin_number_bans++ ] = admin_bans;
  if( script_open )
    g_admin_scripts[ g_admin_number_scripts++ ] = admin_scripts;
  G_Free( file_contents2 );
  ADMP( va( "^3!readconfig: ^7loaded %d levels, %d admins, %d bans, %d scripts\n",
          g_admin_number_levels, g_admin_number_admins, g_admin_number_bans, g_admin_number_scripts ) );
  Com_Printf( "Loaded the following admin levels:\n");
  for( i = 0; i < g_admin_number_levels; i++) {
	  Com_Printf( "Level %d - '%s' - %d commands\n", g_admin_levels[ i ]->level, g_admin_levels[ i ]->name, g_admin_levels[ i ]->num_allowed_commands);
  }
  if( g_admin_number_levels == 0 )
    admin_default_levels();
  else
  {
    char n[ MAX_NAME_LENGTH ] = {""};
    int i = 0;
 
    // max printable name length for formatting 
    for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
    {
      G_DecolorString( admin_levels->name, n );
      if( strlen( n ) > admin_level_maxname )
        admin_level_maxname = strlen( n );
    }
  }
  return qtrue;
}

qboolean G_admin_time( gentity_t *ent, int skiparg )
{
  qtime_t qt;
  int t;

  t = trap_RealTime( &qt );
  ADMP( va( "^3!time: ^7local time is %02i:%02i:%02i\n",
    qt.tm_hour, qt.tm_min, qt.tm_sec ) );
  return qtrue;
}

qboolean G_admin_setlevel( gentity_t *ent, int skiparg )
{
  char name[ MAX_NAME_LENGTH ] = {""};
  char lstr[ 11 ]; // 10 is max strlen() for 32-bit int
  char adminname[ MAX_NAME_LENGTH ] = {""};
  char testname[ MAX_NAME_LENGTH ] = {""};
  char testname2[ MAX_NAME_LENGTH ] = {""};
  char guid[ 33 ];
  int l, i, j;
  gentity_t *vic = NULL;
  qboolean updated = qfalse;
  g_admin_admin_t *a;
  qboolean found = qfalse;
  qboolean numeric = qtrue;
  int matches = 0;
  int id = -1;


  if( G_SayArgc() < 3 + skiparg )
  {
    ADMP( "^3!setlevel: ^7usage: !setlevel [name|slot#] [level]\n" );
    return qfalse;
  }
  G_SayArgv( 1 + skiparg, testname, sizeof( testname ) );
  G_SayArgv( 2 + skiparg, lstr, sizeof( lstr ) );
  l = atoi( lstr );
  G_SanitiseName( testname, name );
  for( i = 0; i < sizeof( name ) && name[ i ] ; i++ )
  {
    if( name[ i ] < '0' || name[ i ] > '9' )
    {
      numeric = qfalse;
      break;
    }
  }
  if( numeric )
    id = atoi( name );

  if( ent && l > ent->client->pers.adminLevel )
  {
    ADMP( "^3!setlevel: ^7you may not use !setlevel to set a level higher "
      "than your current level\n" );
    return qfalse;
  }

  // if admin is activated for the first time on a running server, we need
  // to ensure at least the default levels get created
  if( !ent && !g_admin_levels[ 0 ] )
    G_admin_readconfig(NULL, 0);

  for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
  {
    if( g_admin_levels[ i ]->level == l )
    {
      found = qtrue;
      break;
    }
  }
  if( !found )
  {
    ADMP( "^3!setlevel: ^7level is not defined\n" );
    return qfalse;
  }

  if( numeric  && id >= 0 && id < level.maxclients )
    vic = &g_entities[ id ];

  if( vic && vic->client && vic->client->pers.connected == CON_CONNECTED ) 
  {
    vic = &g_entities[ id ];
    Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) );
    Q_strncpyz( guid, vic->client->pers.guid, sizeof( guid ) );
    matches = 1;
  }
  else if( numeric && id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS
    && g_admin_admins[ id - MAX_CLIENTS ] )
  {
    Q_strncpyz( adminname, g_admin_admins[ id - MAX_CLIENTS ]->name,
      sizeof( adminname ) );
    Q_strncpyz( guid, g_admin_admins[ id - MAX_CLIENTS ]->guid,
      sizeof( guid ) );
    matches = 1;
  }
  else
  {
    for( i = 0; i < level.maxclients && matches < 2; i++ )
    {
      vic = &g_entities[ i ];
      if( !vic->client || vic->client->pers.connected != CON_CONNECTED )
        continue;
      G_SanitiseName( vic->client->pers.netname, testname );
      if( strstr( testname, name ) )
      {
        matches++;
        Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) );
        Q_strncpyz( guid, vic->client->pers.guid, sizeof( guid ) );
      }
    }
    for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && matches < 2; i++)
    {
      G_SanitiseName( g_admin_admins[ i ]->name, testname );
      if( strstr( testname, name ) )
      {
        qboolean dup = qfalse;

        // verify we don't have the same guid/name pair in connected players
        for( j = 0; j < level.maxclients; j++ )
        {
          vic = &g_entities[ j ];
          if( !vic->client || vic->client->pers.connected != CON_CONNECTED )
            continue;
          G_SanitiseName(  vic->client->pers.netname, testname2 );
          if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid )
            && strstr( testname2, name ) ) 
          {
            dup = qtrue;
            break;
          }
        }
        if( dup )
          continue;
        Q_strncpyz( adminname, g_admin_admins[ i ]->name, sizeof( adminname ) );
        Q_strncpyz( guid, g_admin_admins[ i ]->guid, sizeof( guid ) );
        matches++;
      }
    }
  }

  if( matches == 0 )
  {
    ADMP( "^3!setlevel:^7 no match.  use !listplayers or !listadmins to "
      "find an appropriate number to use instead of name.\n" );
    return qfalse;
  }
  else if( matches > 1 )
  {
    ADMP( "^3!setlevel:^7 more than one match.  Use the admin number "
      "instead:\n" );
    admin_listadmins( ent, 0, name, 0 );
    return qfalse;
  }

  if( !Q_stricmp( guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) )
  {
    ADMP( va( "^3!setlevel: ^7%s does not have a valid GUID\n", adminname ) );
    return qfalse;
  }
  if( ent && !admin_higher_guid( ent->client->pers.guid, guid ) )
  {
    ADMP( "^3!setlevel: ^7sorry, but your intended victim has a higher"
        " admin level than you\n" );
    return qfalse;
  }

  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ];i++ )
  {
    if( !Q_stricmp( g_admin_admins[ i ]->guid, guid ) )
    {
      g_admin_admins[ i ]->level = l;
      Q_strncpyz( g_admin_admins[ i ]->name, adminname,
                  sizeof( g_admin_admins[ i ]->name ) );
      updated = qtrue;
    }
  }
  if( !updated )
  {
    if( i == MAX_ADMIN_ADMINS )
    {
      ADMP( "^3!setlevel: ^7too many admins\n" );
      return qfalse;
    }
    a = G_Alloc( sizeof( g_admin_admin_t ) );
    a->level = l;
    Q_strncpyz( a->name, adminname, sizeof( a->name ) );
    Q_strncpyz( a->guid, guid, sizeof( a->guid ) );
    *a->flags = '\0';
    g_admin_admins[ i ] = a;
  }

  AP( va( 
    "print \"^3!setlevel: ^7%s^7 was given level %d admin rights by %s\n\"",
    adminname, l, ( ent ) ? ent->client->pers.netname : "console" ) );
  if( vic )
    vic->client->pers.adminLevel = l;
  
  if( !g_admin.string[ 0 ] )
    ADMP( "^3!setlevel: ^7WARNING g_admin not set, not saving admin record "
      "to a file\n" );
  else
    admin_writeconfig();
  return qtrue;
}

static qboolean admin_create_ban( gentity_t *ent,
  char *netname,
  char *guid,
  char *ip,
  int seconds,
  char *reason ) 
{
  g_admin_ban_t *b = NULL;
  qtime_t qt;
  int t;
  int i;
  int j;
  qboolean foundAdminTrueName=qfalse;

  t = trap_RealTime( &qt );
  b = G_Alloc( sizeof( g_admin_ban_t ) );

  if( !b )
    return qfalse;

  Q_strncpyz( b->name, netname, sizeof( b->name ) );
  Q_strncpyz( b->guid, guid, sizeof( b->guid ) );
  Q_strncpyz( b->ip, ip, sizeof( b->ip ) );

  //strftime( b->made, sizeof( b->made ), "%m/%d/%y %H:%M:%S", lt );
  Q_strncpyz( b->made, va( "%02i/%02i/%02i %02i:%02i:%02i",
    (qt.tm_mon + 1), qt.tm_mday, (qt.tm_year - 100),
    qt.tm_hour, qt.tm_min, qt.tm_sec ),
    sizeof( b->made ) );

  if( ent ) {
    //Get admin true name
    for(j = 0; j < MAX_ADMIN_ADMINS && g_admin_admins[ j ]; j++ )
    {
      if( !Q_stricmp( g_admin_admins[ j ]->guid, ent->client->pers.guid ) )
      {
          Q_strncpyz( b->banner, g_admin_admins[ j ]->name, sizeof( b->banner  ) );
	  foundAdminTrueName=qtrue;
        break;
      }
    }
    if(foundAdminTrueName==qfalse) Q_strncpyz( b->banner, ent->client->pers.netname, sizeof( b->banner ) );
  }
  else
    Q_strncpyz( b->banner, "console", sizeof( b->banner ) );
  if( !seconds )
    b->expires = 0;
  else
    b->expires = t + seconds;
  if( !*reason )
    Q_strncpyz( b->reason, "banned by admin", sizeof( b->reason ) );
  else
    Q_strncpyz( b->reason, reason, sizeof( b->reason ) );
  for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
    ;
  if( i == MAX_ADMIN_BANS )
  {
    ADMP( "^3!ban: ^7too many bans\n" );
    G_Free( b );
    return qfalse;
  }
  g_admin_bans[ i ] = b;
  return qtrue;
}


qboolean G_admin_kick( gentity_t *ent, int skiparg )
{
  int pids[ MAX_CLIENTS ];
  char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];
  int minargc;
  gentity_t *vic;

  minargc = 3 + skiparg;
  if( G_admin_flag_permission( ent, ADMF_UNACCOUNTABLE ) )
    minargc = 2 + skiparg;

  if( G_SayArgc() < minargc )
  {
    ADMP( "^3!kick: ^7usage: !kick [name] [reason]\n" );
    return qfalse;
  }
  G_SayArgv( 1 + skiparg, name, sizeof( name ) );
  reason = G_SayConcatArgs( 2 + skiparg );
  if( G_ClientNumbersFromString( name, pids ) != 1 )
  {
    G_MatchOnePlayer( pids, err, sizeof( err ) );
    ADMP( va( "^3!kick: ^7%s\n", err ) );
    return qfalse;
  }
  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
  {
    ADMP( "^3!kick: ^7sorry, but your intended victim has a higher admin"
        " level than you\n" );
    return qfalse;
  }
  vic = &g_entities[ pids[ 0 ] ];
  if( g_adminTempBan.integer > 0 )
  {
    admin_create_ban( ent,
      vic->client->pers.netname,
      vic->client->pers.guid,
      vic->client->pers.ip, g_adminTempBan.integer,
      "automatic temp ban created by kick" );
    if( g_admin.string[ 0 ] )
      admin_writeconfig();
  }
  
  trap_SendServerCommand( pids[ 0 ],
    va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\"",
      ( ent ) ? va( "admin:\n%s", ent->client->pers.netname ) : "",
      ( *reason ) ? reason : "kicked by admin" ) );

  trap_DropClient( pids[ 0 ], va( "has been kicked%s^7. reason: %s",
    ( ent ) ? va( " by %s", ent->client->pers.netname ) : "",
    ( *reason ) ? reason : "kicked by admin" ) );

  return qtrue;
}

int G_admin_parse_time( const char *time )
{
  int seconds = 0, num = 0;
  int i;

  for( i = 0; time[ i ]; i++ )
  {
    if( time[ i ] >= '0' && time[ i ] <= '9' )
    {
      num = num * 10 + time[ i ] - '0';
      continue;
    }
    switch( time[ i ] )
    {
      case 'w': num *= 7;
      case 'd': num *= 24;
      case 'h': num *= 60;
      case 'm': num *= 60;
      case 's': break;
      default:  return -1;
    }
    seconds += num;
    num = 0;
  }
  if (num)
    seconds += num;
  return seconds;
}

qboolean G_admin_ban( gentity_t *ent, int skiparg )
{
  int seconds;
  char search[ MAX_NAME_LENGTH ];
  char secs[ 7 ];
  char *reason;
  int minargc;
  char duration[ 32 ];
  int logmatch = -1, logmatches = 0;
  int i, j;
  qboolean exactmatch = qfalse;
  char n2[ MAX_NAME_LENGTH ];
  char s2[ MAX_NAME_LENGTH ];
  char guid_stub[ 9 ];

  if( G_admin_flag_permission( ent, ADMF_CAN_PERM_BAN ) &&
       G_admin_flag_permission( ent, ADMF_UNACCOUNTABLE ) )
  {
    minargc = 2 + skiparg;
  }
  else if( G_admin_flag_permission( ent, ADMF_CAN_PERM_BAN ) ||
            G_admin_flag_permission( ent, ADMF_UNACCOUNTABLE ) )
  {
    minargc = 3 + skiparg;
  }
  else
  {
    minargc = 4 + skiparg;
  }
  if( G_SayArgc() < minargc )
  {
    ADMP( "^3!ban: ^7usage: !ban [name|slot|ip] [seconds] [reason]\n" );
    return qfalse;
  }
  G_SayArgv( 1 + skiparg, search, sizeof( search ) );
  G_SanitiseName( search, s2 );
  G_SayArgv( 2 + skiparg, secs, sizeof( secs ) );

  seconds = G_admin_parse_time( secs );
  if( seconds <= 0 )
  {
    if( G_admin_flag_permission( ent, ADMF_CAN_PERM_BAN ) )
    {
      seconds = 0;
    }
    else
    {
      ADMP( "^3!ban: ^7ban time must be positive\n" );
      return qfalse;
    }
    reason = G_SayConcatArgs( 2 + skiparg );
  }
  else
  {
    reason = G_SayConcatArgs( 3 + skiparg );
  }

  for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
  {
    // skip players in the namelog who have already been banned
    if( g_admin_namelog[ i ]->banned )
      continue;

    // skip disconnected players when banning on slot number
    if( g_admin_namelog[ i ]->slot == -1 )
      continue;

    if( !Q_stricmp( va( "%d", g_admin_namelog[ i ]->slot ), s2 ) )
    {
      logmatches = 1;
      logmatch = i;
      exactmatch = qtrue;
      break;
    }
  } 

  for( i = 0;
       !exactmatch && i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ];
       i++ )
  {
    // skip players in the namelog who have already been banned
    if( g_admin_namelog[ i ]->banned )
      continue;

    if( !Q_stricmp( g_admin_namelog[ i ]->ip, s2 ) )
    {
      logmatches = 1;
      logmatch = i;
      exactmatch = qtrue;
      break;
    }
    for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES
      && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
    {
      G_SanitiseName(g_admin_namelog[ i ]->name[ j ], n2);
      if( strstr( n2, s2 ) )
      {
        if( logmatch != i )
          logmatches++;
        logmatch = i; 
      }
    }
  }
  
  if( !logmatches ) 
  {
    ADMP( "^3!ban: ^7no player found by that name, IP, or slot number\n" );
    return qfalse;
  } 
  else if( logmatches > 1 )
  {
    ADMBP_begin();
    ADMBP( "^3!ban: ^7multiple recent clients match name, use IP or slot#:\n" );
    for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
    {
      for( j = 0; j <= 8; j++ )
        guid_stub[ j ] = g_admin_namelog[ i ]->guid[ j + 24 ];
      guid_stub[ j ] = '\0';
      for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES
        && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
      {
        G_SanitiseName(g_admin_namelog[ i ]->name[ j ], n2);
        if( strstr( n2, s2 ) )
        {
          if( g_admin_namelog[ i ]->slot > -1 )
            ADMBP( "^3" );
          ADMBP( va( "%-2s (*%s) %15s ^7'%s^7'\n",
           (g_admin_namelog[ i ]->slot > -1) ?
             va( "%d", g_admin_namelog[ i ]->slot ) : "-",
           guid_stub,
           g_admin_namelog[ i ]->ip,
           g_admin_namelog[ i ]->name[ j ] ) );
        }
      }
    }
    ADMBP_end();
    return qfalse;
  }
  
  G_admin_duration( ( seconds ) ? seconds : -1,
    duration, sizeof( duration ) );

  if( ent && !admin_higher_guid( ent->client->pers.guid,
    g_admin_namelog[ logmatch ]->guid ) )
  {

    ADMP( "^3!ban: ^7sorry, but your intended victim has a higher admin"
      " level than you\n" );
    return qfalse;
  }

  admin_create_ban( ent,
    g_admin_namelog[ logmatch ]->name[ 0 ],
    g_admin_namelog[ logmatch ]->guid,
    g_admin_namelog[ logmatch ]->ip,
    seconds, reason ); 

  g_admin_namelog[ logmatch ]->banned = qtrue;

  if( !g_admin.string[ 0 ] )
    ADMP( "^3!ban: ^7WARNING g_admin not set, not saving ban to a file\n" );
  else
    admin_writeconfig();

  if(g_admin_namelog[ logmatch ]->slot == -1 ) 
  {
    // client is already disconnected so stop here
    AP( va( "print \"^3!ban:^7 %s^7 has been banned by %s^7 "
      "duration: %s, reason: %s\n\"",
      g_admin_namelog[ logmatch ]->name[ 0 ],
      ( ent ) ? ent->client->pers.netname : "console",
      duration,
      ( *reason ) ? reason : "banned by admin" ) );
    return qtrue;
  }

  trap_SendServerCommand( g_admin_namelog[ logmatch ]->slot,
    va( "disconnect \"You have been banned.\n"
      "admin:\n%s^7\nduration:\n%s\nreason:\n%s\"",
      ( ent ) ? ent->client->pers.netname : "console",
      duration,
      ( *reason ) ? reason : "kicked by admin" ) );

  trap_DropClient(  g_admin_namelog[ logmatch ]->slot,
    va( "has been banned by %s^7 duration: %s, reason: %s",
      ( ent ) ? ent->client->pers.netname : "console",
      duration,
      ( *reason ) ? reason : "banned by admin" ) );
  return qtrue;
}

qboolean G_admin_adjustban( gentity_t *ent, int skiparg )
{
  int bnum;
  int length;
  char *arg;
  char bs[ 5 ];

  if( G_SayArgc() < 2 + skiparg )
  {
    ADMP( "^3!adjustban: ^7usage: !adjustban [ban #] [length|reason]\n" );
    return qfalse;
  }
  G_SayArgv( 1 + skiparg, bs, sizeof( bs ) );
  bnum = atoi( bs );
  if( bnum < 1 || !g_admin_bans[ bnum - 1] )
  {
    ADMP( "^3!adjustban: ^7invalid ban #\n" );
    return qfalse;
  }

  arg = G_SayConcatArgs( 2 + skiparg );
  length = G_admin_parse_time( arg );
  if( G_SayArgc() > 3 + skiparg || length < 0) // reason
  {
    Q_strncpyz( g_admin_bans[ bnum - 1 ]->reason, arg,
      sizeof( g_admin_bans[ bnum - 1 ]->reason ) );
    AP( va( "print \"^3!adjustban: ^7ban #%d reason changed to %s\n\"", bnum,
            arg ) );
  }
  else // time
  {
    int expires;
    char duration[ 32 ];
    if( length )
    {
      qtime_t qt;
      expires = trap_RealTime( &qt ) + length;
    }
    else
      expires = 0;

    if( g_admin_bans[ bnum - 1 ]->expires == expires )
    {
      ADMP( "^3!adjustban: ^7no change\n" );
      return qfalse;
    }
    g_admin_bans[ bnum - 1 ]->expires = expires;
    G_admin_duration( expires - ( expires - length ), duration, sizeof( duration ) );

    AP( va( "print \"^3!adjustban: ^7ban #%d length adjusted to %s\n\"", bnum,
            duration ) );
  }

  if( !g_admin.string[ 0 ] )
    ADMP( "^3!adjustban: ^7WARNING g_admin not set, not saving ban to a file\n" );
  else
    admin_writeconfig();
  return qtrue;
}

qboolean G_admin_unban( gentity_t *ent, int skiparg )
{
  int bnum;
  char bs[ 5 ];
  qtime_t qt;
  int t;

  t = trap_RealTime( &qt );
  if( G_SayArgc() < 2 + skiparg )
  {
    ADMP( "^3!unban: ^7usage: !unban [ban #]\n" );
    return qfalse;
  }
  G_SayArgv( 1 + skiparg, bs, sizeof( bs ) );
  bnum = atoi( bs );
  if( bnum < 1 || !g_admin_bans[ bnum - 1 ] )
  {
    ADMP( "^3!unban: ^7invalid ban #\n" );
    return qfalse;
  }
  g_admin_bans[ bnum -1 ]->expires = t;
  AP( va( "print \"^3!unban: ^7ban #%d for %s^7 has been removed by %s\n\"",
          bnum,
          g_admin_bans[ bnum - 1 ]->name,
          ( ent ) ? ent->client->pers.netname : "console" ) );
  if( g_admin.string[ 0 ] )
    admin_writeconfig();
  return qtrue;
}

qboolean G_admin_putteam( gentity_t *ent, int skiparg )
{
  int pids[ MAX_CLIENTS ];
  char name[ MAX_NAME_LENGTH ], team[ 7 ], err[ MAX_STRING_CHARS ];
  gentity_t *vic;
  pTeam_t teamnum = PTE_NONE;
  char teamdesc[ 32 ] = {"spectators"};

  G_SayArgv( 1 + skiparg, name, sizeof( name ) );
  G_SayArgv( 2 + skiparg, team, sizeof( team ) );
  if( G_SayArgc() < 3 + skiparg )
  {
    ADMP( "^3!putteam: ^7usage: !putteam [name] [h|a|s]\n" );
    return qfalse;
  }

  if( G_ClientNumbersFromString( name, pids ) != 1 )
  {
    G_MatchOnePlayer( pids, err, sizeof( err ) );
    ADMP( va( "^3!putteam: ^7%s\n", err ) );
    return qfalse;
  }
  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
  {
    ADMP( "^3!putteam: ^7sorry, but your intended victim has a higher "
        " admin level than you\n" );
    return qfalse;
  }
  vic = &g_entities[ pids[ 0 ] ];
  switch( team[ 0 ] )
  {
  case 'a':
    teamnum = PTE_ALIENS;
    Q_strncpyz( teamdesc, "aliens", sizeof( teamdesc ) );
    break;
  case 'h':
    teamnum = PTE_HUMANS;
    Q_strncpyz( teamdesc, "humans", sizeof( teamdesc ) );
    break;
  case 's':
    teamnum = PTE_NONE;
    break;
  default:
    ADMP( va( "^3!putteam: ^7unknown team %c\n", team[ 0 ] ) );
    return qfalse;
  }
  if( vic->client->pers.teamSelection == teamnum )
    return qfalse;
  G_ChangeTeam( vic, teamnum );

  AP( va( "print \"^3!putteam: ^7%s^7 put %s^7 on to the %s team\n\"",
          ( ent ) ? ent->client->pers.netname : "console",
          vic->client->pers.netname, teamdesc ) );
  return qtrue;
}

qboolean G_admin_map( gentity_t *ent, int skiparg )
{
  char map[ MAX_QPATH ];
  char layout[ MAX_QPATH ] = { "" };
  
  if( G_SayArgc( ) < 2 + skiparg )
  {
    if(g_cheats.integer){
        trap_Cvar_Set( "sv_cheats", "0" );
        AP( va( "print \"^3!devmap:^7 Cheats turned ^1off^7 by %s^7\n\"", ( ent ) ? ent->client->pers.netname : "console"));
        return qtrue;
    } else {
        ADMP( "^3!devmap:^7 Cheats already ^1off\n");
        return qfalse;
    }
  }

  G_SayArgv( skiparg + 1, map, sizeof( map ) );

  if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) )
  {
    ADMP( va( "^3!map: ^7invalid map name '%s'\n", map ) );
    return qfalse;
  }

  if( G_SayArgc( ) > 2 + skiparg )
  {
	  //get the layout
	  G_SayArgv( skiparg + 2, layout, sizeof( layout ) );
	  if( !Q_stricmp( layout, "*BUILTIN*" ) ) {
		  ADMP( "Using built-in layout\n" );
	  } else {
		  int file_open = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), NULL, FS_READ );
		  if( file_open <= 0 )
		  {
			  ADMP( va( "^3!map: ^7invalid layout name '%s'\n", layout ) );
			  return qfalse;
		  } else {
			  ADMP( va( "Using layout '%s'. File_open was %d\n", layout, file_open));
		  }
	  }
  }
  AP( va( "print \"^3!map: ^7map '%s' started by %s^7 %s\n\"", map,
            ( ent ) ? ent->client->pers.netname : "console",
            ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) );
  if(Q_stricmp(layout, "")) {
	  Com_Printf( "Calling map '%s' '%s'\n", map, layout );
	  trap_SendConsoleCommand( EXEC_APPEND, va( "map %s %s", map, layout ) );
  }
  else {
	  Com_Printf( "Calling map '%s'\n", map );
	  trap_SendConsoleCommand( EXEC_APPEND, va( "map %s", map ) );
  }
  
  level.numBots = 0;
  level.restarted = qtrue;
  return qtrue;
}

qboolean G_admin_devmap( gentity_t *ent, int skiparg )
{
  char map[ MAX_QPATH ];
  char layout[ MAX_QPATH ] = { "" };

  if( G_SayArgc( ) < 2 + skiparg )
  {
    if(!g_cheats.integer){
        trap_Cvar_Set( "sv_cheats", "1" );
        AP( va( "print \"^3!devmap:^7 Cheats turned ^2on^7 by %s^7\n\"", ( ent ) ? ent->client->pers.netname : "console"));
        //trap_SendConsoleCommand( EXEC_APPEND, va( "!print \"^3!devmap:^7 Cheats turned ^2on^7 by %s^7\n\"", ( ent ) ? ent->client->pers.netname : "console"));
        /*ADMP( "^3!devmap: ^7usage: !devmap [map] (layout)\n" );*/
        return qtrue;
    } else {
        ADMP( "^3!devmap:^7 Cheats already ^2on\n");
        return qfalse;
    }
  }
  
  G_SayArgv( skiparg + 1, map, sizeof( map ) );

  if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) )
  {
    ADMP( va( "^3!devmap: ^7invalid map name '%s'\n", map ) );
    return qfalse;
  }

  if( G_SayArgc( ) > 2 + skiparg )
  {
    G_SayArgv( skiparg + 2, layout, sizeof( layout ) );
    if( !Q_stricmp( layout, "*BUILTIN*" ) ||
      trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ),
        NULL, FS_READ ) > 0 )
    {
      trap_Cvar_Set( "g_layouts", layout );
    }
    else
    {
      ADMP( va( "^3!devmap: ^7invalid layout name '%s'\n", layout ) );
      return qfalse;
    }
  }

  trap_SendConsoleCommand( EXEC_APPEND, va( "devmap %s", map ) );
  level.restarted = qtrue;
  level.numBots = 0;
  AP( va( "print \"^3!devmap: ^7map '%s' started by %s^7 with cheats %s\n\"", map,
          ( ent ) ? ent->client->pers.netname : "console",
          ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) );
  return qtrue;
}

qboolean G_admin_layoutsave( gentity_t *ent, int skiparg )
{
  char layout[ MAX_QPATH ];

  if( G_SayArgc( ) < 2 + skiparg )
  {
    ADMP( "^3!layoutsave: ^7usage: !layoutsave [layout]\n" );
    return qfalse;
  }

  G_SayArgv( skiparg + 1, layout, sizeof( layout ) );

  trap_SendConsoleCommand( EXEC_APPEND, va( "layoutsave %s", layout ) );
  AP( va( "print \"^3!layoutsave: ^7layout saved as '%s' by %s\n\"", layout,
          ( ent ) ? ent->client->pers.netname : "console" ) );
  return qtrue;
}

qboolean G_admin_mute( gentity_t *ent, int skiparg )
{
  int pids[ MAX_CLIENTS ];
  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
  char command[ MAX_ADMIN_CMD_LEN ], *cmd;
  gentity_t *vic;

  if( G_SayArgc() < 2 + skiparg )
  {
    ADMP( "^3!mute: ^7usage: !mute [name|slot#]\n" );
    return qfalse;
  }
  G_SayArgv( skiparg, command, sizeof( command ) );
  cmd = command;
  if( cmd && *cmd == '!' )
    cmd++;
  G_SayArgv( 1 + skiparg, name, sizeof( name ) );
  if( G_ClientNumbersFromString( name, pids ) != 1 )
  {
    G_MatchOnePlayer( pids, err, sizeof( err ) );
    ADMP( va( "^3!mute: ^7%s\n", err ) );
    return qfalse;
  }
  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
  {
    ADMP( "^3!mute: ^7sorry, but your intended victim has a higher admin"
        " level than you\n" );
    return qfalse;
  }
  vic = &g_entities[ pids[ 0 ] ];
  if( vic->client->pers.muted == qtrue )
  {
    if( !Q_stricmp( cmd, "mute" ) )
    {
      ADMP( "^3!mute: ^7player is already muted\n" );
      return qtrue;
    }
    vic->client->pers.muted = qfalse;
    CPx( pids[ 0 ], "cp \"^1You have been unmuted\"" );
    AP( va( "print \"^3!unmute: ^7%s^7 has been unmuted by %s\n\"",
            vic->client->pers.netname,
            ( ent ) ? ent->client->pers.netname : "console" ) );
  }
  else
  {
    if( !Q_stricmp( cmd, "unmute" ) )
    {
      ADMP( "^3!unmute: ^7player is not currently muted\n" );
      return qtrue;
    }
    vic->client->pers.muted = qtrue;
    CPx( pids[ 0 ], "cp \"^1You've been muted\"" );
    AP( va( "print \"^3!mute: ^7%s^7 has been muted by ^7%s\n\"",
            vic->client->pers.netname,
            ( ent ) ? ent->client->pers.netname : "console" ) );
  }
  ClientUserinfoChanged( pids[ 0 ] );
  return qtrue;
}

qboolean G_admin_denybuild( gentity_t *ent, int skiparg )
{
  int pids[ MAX_CLIENTS ];
  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
  char command[ MAX_ADMIN_CMD_LEN ], *cmd;
  gentity_t *vic;

  G_SayArgv( skiparg, command, sizeof( command ) );
  cmd = command;
  if( cmd && *cmd == '!' )
    cmd++;
  if( G_SayArgc() < 2 + skiparg )
  {
    ADMP( va( "^3!%s: ^7usage: !%s [name|slot#]\n", cmd, cmd ) );
    return qfalse;
  }
  G_SayArgv( 1 + skiparg, name, sizeof( name ) );
  if( G_ClientNumbersFromString( name, pids ) != 1 )
  {
    G_MatchOnePlayer( pids, err, sizeof( err ) );
    ADMP( va( "^3!%s: ^7%s\n", cmd, err ) );
    return qfalse;
  }
  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
  {
    ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin"
              " level than you\n", cmd ) );
    return qfalse;
  }
  vic = &g_entities[ pids[ 0 ] ];
  if( vic->client->pers.denyBuild )
  {
    if( !Q_stricmp( cmd, "denybuild" ) )
    {
      ADMP( "^3!denybuild: ^7player already has no building rights\n" );
      return qtrue;
    }
    vic->client->pers.denyBuild = qfalse;
    CPx( pids[ 0 ], "cp \"^1You've regained your building rights\"" );
    AP( va(
      "print \"^3!allowbuild: ^7building rights for ^7%s^7 restored by %s\n\"",
      vic->client->pers.netname,
      ( ent ) ? ent->client->pers.netname : "console" ) );
  }
  else
  {
    if( !Q_stricmp( cmd, "allowbuild" ) )
    {
      ADMP( "^3!allowbuild: ^7player already has building rights\n" );
      return qtrue;
    }
    vic->client->pers.denyBuild = qtrue;
    vic->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
    if( vic->client->ps.stats[ STAT_PCLASS ]== PCL_ALIEN_BUILDER0 || ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG )
    {
      vic->client->ps.stats[ STAT_HEALTH ] = vic->health = 0;
      player_die( vic, vic, vic, 100000, MOD_SUICIDE );
    }
    CPx( pids[ 0 ], "cp \"^1You've lost your building rights\"" );
    AP( va(
      "print \"^3!denybuild: ^7building rights for ^7%s^7 revoked by ^7%s\n\"",
      vic->client->pers.netname,
      ( ent ) ? ent->client->pers.netname : "console" ) );
  }
  ClientUserinfoChanged( pids[ 0 ] );
  return qtrue;
}

qboolean G_admin_listadmins( gentity_t *ent, int skiparg )
{
  int i, found = 0;
  qtime_t qt;
  int t;
  char search[ MAX_NAME_LENGTH ] = {""};
  char s[ MAX_NAME_LENGTH ] = {""};
  int start = 0;
  qboolean numeric = qtrue;
  int drawn = 0;
  int minlevel = 1;

  t = trap_RealTime( &qt );
  
  if( G_SayArgc() == 3 + skiparg )
  {
    G_SayArgv( 2 + skiparg, s, sizeof( s ) );
    if( s[ 0 ] >= '0' && s[ 0] <= '9' )
    {
      minlevel = atoi( s );
      if( minlevel < 1 ) 
       minlevel = 1;
    }
  }

  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
  {
    if( g_admin_admins[ i ]->level >= minlevel )
      found++;
  }
  if( !found )
  {
    if( minlevel > 1 )
    {
      ADMP( va( "^3!listadmins: ^7no admins level %i or greater found\n", minlevel ) );
    }
    else
    {
       ADMP( "^3!listadmins: ^7no admins defined\n" );
    }
    return qfalse;
  }

  if( G_SayArgc() >= 2 + skiparg )
  {
    G_SayArgv( 1 + skiparg, s, sizeof( s ) );
    for( i = 0; i < sizeof( s ) && s[ i ]; i++ )
    {
      if( s[ i ] >= '0' && s[ i ] <= '9' )
        continue;
      numeric = qfalse; 
    }
    if( numeric ) 
    {
      start = atoi( s );
      if( start > 0 )
        start -= 1;
      else if( start < 0 )
        start = found + start;
    }
    else
      G_SanitiseName( s, search );
  }

  if( start >= found || start < 0 )
    start = 0;

  drawn = admin_listadmins( ent, start, search, minlevel );

  if( search[ 0 ] )
  {
    if( drawn <= 20 )
    {
      ADMP( va( "^3!listadmins:^7 found %d admins level %i or greater matching '%s^7'\n",
        drawn, minlevel, search ) );
    }
    else
    {
      ADMP( va( "^3!listadmins:^7 found >20 admins level %i or greater matching '%s^7. Try a more narrow search.'\n",
        minlevel, search ) );
    }
  }
  else
  {
    ADMBP_begin();
    ADMBP( va( "^3!listadmins:^7 showing admins level %i or greater %d - %d of %d.  ",
      minlevel,
      ( found ) ? ( start + 1 ) : 0,
      ( ( start + MAX_ADMIN_LISTITEMS ) > found ) ?
       found : ( start + MAX_ADMIN_LISTITEMS ),
      found ) );
    if( ( start + MAX_ADMIN_LISTITEMS ) < found )
    {
      if( minlevel > 1)
      {
        ADMBP( va( "run '!listadmins %d %d' to see more",
          ( start + MAX_ADMIN_LISTITEMS + 1 ), minlevel ) );
      }
      else
      {
        ADMBP( va( "run '!listadmins %d' to see more",
          ( start + MAX_ADMIN_LISTITEMS + 1 ) ) );
      }
    }
    ADMBP( "\n" );
    ADMBP_end();
  }
  return qtrue;
}

qboolean G_admin_listlayouts( gentity_t *ent, int skiparg )
{
  char list[ MAX_CVAR_VALUE_STRING ];
  char map[ MAX_QPATH ];
  int count = 0;
  char *s;
  char layout[ MAX_QPATH ] = { "" };
  int i = 0;
  
  if( G_SayArgc( ) == 2 + skiparg ) 
    G_SayArgv( 1 +skiparg, map, sizeof( map ) );
  else
    trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
  
  count = G_LayoutList( map, list, sizeof( list ) );
  ADMBP_begin( );
  ADMBP( va( "^3!listlayouts:^7 %d layouts found for '%s':\n", count, map ) );
  s = &list[ 0 ];
  while( *s )
  {
    if( *s == ' ' )
    {
      ADMBP( va ( " %s\n", layout ) );
      layout[ 0 ] = '\0';
      i = 0;
    }
    else if( i < sizeof( layout ) - 2 )
    {
      layout[ i++ ] = *s;
      layout[ i ] = '\0';
    }
    s++;
  }
  if( layout[ 0 ] )
    ADMBP( va ( " %s\n", layout ) );
  ADMBP_end( );
  return qtrue;
}

qboolean G_admin_listplayers( gentity_t *ent, int skiparg )
{
  int i, j;
  gclient_t *p;
  char c[ 3 ], t[ 2 ]; // color and team letter
  char n[ MAX_NAME_LENGTH ] = {""};
  char n2[ MAX_NAME_LENGTH ] = {""};
  char n3[ MAX_NAME_LENGTH ] = {""};
  char lname[ MAX_NAME_LENGTH ];
  char lname2[ MAX_NAME_LENGTH ];
  char guid_stub[ 9 ];
  char muted[ 2 ], denied[ 2 ], dbuilder[ 2 ];
  int l;
  char lname_fmt[ 5 ];

  ADMBP_begin();
  ADMBP( va( "^3!listplayers^7: %d players connected:\n",
    level.numConnectedClients ) );
  for( i = 0; i < level.maxclients; i++ )
  {
    p = &level.clients[ i ];
    Q_strncpyz( t, "S", sizeof( t ) );
    Q_strncpyz( c, S_COLOR_YELLOW, sizeof( c ) );
    if( p->pers.teamSelection == PTE_HUMANS )
    {
      Q_strncpyz( t, "H", sizeof( t ) );
      Q_strncpyz( c, S_COLOR_BLUE, sizeof( c ) );
    }
    else if( p->pers.teamSelection == PTE_ALIENS )
    {
      Q_strncpyz( t, "A", sizeof( t ) );
      Q_strncpyz( c, S_COLOR_RED, sizeof( c ) );
    }

    if( p->pers.connected == CON_CONNECTING )
    {
      Q_strncpyz( t, "C", sizeof( t ) );
      Q_strncpyz( c, S_COLOR_CYAN, sizeof( c ) );
    }
    else if( p->pers.connected != CON_CONNECTED )
    {
      continue;
    }

    for( j = 0; j <= 8; j++ )
      guid_stub[ j ] = p->pers.guid[ j + 24 ];
    guid_stub[ j ] = '\0';

    muted[ 0 ] = '\0';
    if( p->pers.muted )
    {
      Q_strncpyz( muted, "M", sizeof( muted ) );
    }
    denied[ 0 ] = '\0';
    if( p->pers.denyBuild )
    {
      Q_strncpyz( denied, "B", sizeof( denied ) );
    }

    dbuilder[ 0 ] = '\0';
    if( p->pers.designatedBuilder )
    {
      if( !G_admin_flag_permission( &g_entities[ i ], ADMF_INCOGNITO ) &&
          G_admin_flag_permission( &g_entities[ i ], ADMF_DBUILDER ) && 
          G_admin_flag_permission(ent, ADMF_SEESFULLLISTPLAYERS ) )
      {
        Q_strncpyz( dbuilder, "P", sizeof( dbuilder ) );
      }
      else
      {
        Q_strncpyz( dbuilder, "D", sizeof( dbuilder ) );
      }
    }

    l = 0;
    G_SanitiseName( p->pers.netname, n2 );
    n[ 0 ] = '\0';
    for( j = 0; j < MAX_ADMIN_ADMINS && g_admin_admins[ j ]; j++ )
    {
      if( !Q_stricmp( g_admin_admins[ j ]->guid, p->pers.guid ) )
      {

        // don't gather aka or level info if the admin is incognito
        if( G_admin_flag_permission( &g_entities[ i ], ADMF_INCOGNITO ) )
        {
          break;
        }
        l = g_admin_admins[ j ]->level;
        G_SanitiseName( g_admin_admins[ j ]->name, n3 );
        if( Q_stricmp( n2, n3 ) )
        {
          Q_strncpyz( n, g_admin_admins[ j ]->name, sizeof( n ) );
        }
        break;
      }
    }
    lname[ 0 ] = '\0';
    Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) );
    for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
    {
      if( g_admin_levels[ j ]->level == l )
      {
        Q_strncpyz( lname, g_admin_levels[ j ]->name, sizeof( lname ) );
        if( *lname )
        {
          G_DecolorString( lname, lname2 );
          Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is",
            ( admin_level_maxname + strlen( lname ) - strlen( lname2 ) ) );
          Com_sprintf( lname2, sizeof( lname2 ), lname_fmt, lname );
        }
        break;
      }

    }

     
     if( G_admin_flag_permission(ent, ADMF_SEESFULLLISTPLAYERS ) ) {
 
      ADMBP( va( "%2i %s%s^7 %-2i %s^7 (*%s) ^1%1s%1s%1s^7 %s^7 %s%s^7%s\n",
               i,
               c,
               t,
               l,
               ( *lname ) ? lname2 : "", 
               guid_stub,
               muted,
	       dbuilder,
               denied,
               p->pers.netname,
               ( *n ) ? "(a.k.a. " : "",
               n,
               ( *n ) ? ")" : ""
             ) );
     }
     else
     {
      ADMBP( va( "%2i %s%s^7 ^1%1s%1s%1s^7 %s^7\n",
               i,
               c,
               t,
               muted,
               dbuilder,
               denied,
               p->pers.netname
             ) );
     }
  }
  ADMBP_end();
  return qtrue;
}

#define MAX_LISTMAPS_MAPS 400

static int SortMaps2(const void *a, const void *b)
{
  return strcmp(*(char **)a, *(char **)b);
}

qboolean G_admin_listmaps( gentity_t *ent, int skiparg )
{
  char fileList[ 4096 ] = {""};
  char *fileSort[ MAX_LISTMAPS_MAPS ];
  long int numFiles;
  long int i;
  long int fileLen = 0;
  long int  count = 0;
  char *filePtr;
  long int rows;
	int botmaps = 0;
  numFiles = trap_FS_GetFileList( "maps/", ".bsp",
    fileList, sizeof( fileList ) );
  filePtr = fileList;
  for( i = 0; i < numFiles && count < MAX_LISTMAPS_MAPS; i++, filePtr += fileLen + 1 )
  {
    fileLen = strlen( filePtr );
    if (fileLen < 5)
      continue;

    filePtr[ fileLen - 4 ] = '\0';
    fileSort[ count ] = filePtr;
    count++;
  }

  qsort(fileSort, count, sizeof(fileSort[ 0 ]), SortMaps2);

  rows = count / 3;
  if ( rows * 3 < count ) rows++;

  ADMBP_begin();
  ADMBP( "^3----------------------------^7\n" );
  for( i = 0; i < rows; i++ )
  {
    //ADMBP( va( "^7%20s %20s %20s\n",
    if(!trap_FS_FOpenFile( va( "maps/%s.aas", fileSort[ i ] ), NULL, FS_READ ))
    	ADMBP( va( "^7%20s ", fileSort[ i ]) );
    else {
    	ADMBP( va( "^2%20s ", fileSort[ i ]) );
    	botmaps++;
    }
    if(!trap_FS_FOpenFile( va( "maps/%s.aas", fileSort[ rows + i ] ), NULL, FS_READ ))
    	ADMBP( va( "^7%20s ",( rows + i < count ) ? fileSort[ rows + i ] : "") );
    else {
    	ADMBP( va( "^2%20s ",( rows + i < count ) ? fileSort[ rows + i ] : "") );
    	botmaps++;
    }
    if(!trap_FS_FOpenFile( va( "maps/%s.aas", fileSort[ rows * 2 + i ] ), NULL, FS_READ ))
    	ADMBP( va( "^7%20s\n",( rows * 2 + i < count ) ? fileSort[ rows * 2 + i ] : "" ) );
    else {
    	ADMBP( va( "^2%20s\n",( rows * 2 + i < count ) ? fileSort[ rows * 2 + i ] : "" ) );
    	botmaps++;
    }
  }
  ADMBP("Legend: ^2bot compatible^7 | not bot compatible\n");
  ADMBP( va( "^3!listmaps: ^7listing ^2%d^7 bot compatible maps.\n", botmaps ) );
  ADMBP( "^3----------------------------^7\n" );
  ADMBP_end();

  return qtrue;
}

qboolean G_admin_showbans( gentity_t *ent, int skiparg )
{
  int i, found = 0;
  qtime_t qt;
  int t;
  char duration[ 32 ];
  char name_fmt[ 32 ] = { "%s" };
  char banner_fmt[ 32 ] = { "%s" };
  int max_name = 1, max_banner = 1;
  int secs;
  int start = 0;
  char skip[ 11 ];
  char date[ 11 ];
  char *made;
  int j;
  char n1[ MAX_NAME_LENGTH ] = {""};
  char n2[ MAX_NAME_LENGTH ] = {""};

  t = trap_RealTime( &qt );

  for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
  {
    if( g_admin_bans[ i ]->expires != 0
      && ( g_admin_bans[ i ]->expires - t ) < 1 )
    {
      continue;
    }
    found++;
  }

  if( G_SayArgc() < 3 + skiparg )
  {
    G_SayArgv( 1 + skiparg, skip, sizeof( skip ) );
    start = atoi( skip );
    // showbans 1 means start with ban 0
    if( start > 0 )
      start -= 1;
    else if( start < 0 )
      start = found + start;
  }

  if( start >= MAX_ADMIN_BANS || start < 0 )
    start = 0;

  for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ] 
    && ( i - start ) < MAX_ADMIN_SHOWBANS; i++ )
  {
    G_DecolorString( g_admin_bans[ i ]->name, n1 );
    G_DecolorString( g_admin_bans[ i ]->banner, n2 );
    if( strlen( n1 ) > max_name )
    {
      max_name = strlen( n1 );
    }
    if( strlen( n2 ) > max_banner )
      max_banner = strlen( n2 );
  }

  if( start >= found )
  {
    ADMP( va( "^3!showbans: ^7there are %d active bans\n", found ) );
    return qfalse;
  }
  ADMBP_begin();
  for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ]
    && ( i - start ) < MAX_ADMIN_SHOWBANS; i++ )
  {
    if( g_admin_bans[ i ]->expires != 0
      && ( g_admin_bans[ i ]->expires - t ) < 1 )
      continue;

    // only print out the the date part of made
    date[ 0 ] = '\0';
    made = g_admin_bans[ i ]->made;
    for( j = 0; made && *made; j++ )
    {
      if( ( j + 1 ) >= sizeof( date ) )
        break;
      if( *made == ' ' )
        break;
      date[ j ] = *made;
      date[ j + 1 ] = '\0';
      made++;
    }

    secs = ( g_admin_bans[ i ]->expires - t );
    G_admin_duration( secs, duration, sizeof( duration ) );

    G_DecolorString( g_admin_bans[ i ]->name, n1 );
    Com_sprintf( name_fmt, sizeof( name_fmt ), "%%%is",
      ( max_name + strlen( g_admin_bans[ i ]->name ) - strlen( n1 ) ) );
    Com_sprintf( n1, sizeof( n1 ), name_fmt, g_admin_bans[ i ]->name ); 

    G_DecolorString( g_admin_bans[ i ]->banner, n2 );
    Com_sprintf( banner_fmt, sizeof( banner_fmt ), "%%%is",
      ( max_banner + strlen( g_admin_bans[ i ]->banner ) - strlen( n2 ) ) );
    Com_sprintf( n2, sizeof( n2 ), banner_fmt, g_admin_bans[ i ]->banner ); 

    ADMBP( va( "%4i %s^7 %-15s %-8s %s^7 %-10s\n     \\__ %s\n",
             ( i + 1 ),
             n1,
             g_admin_bans[ i ]->ip,
             date,
             n2,
             duration,
             g_admin_bans[ i ]->reason ) );
  }

  ADMBP( va( "^3!showbans:^7 showing bans %d - %d of %d.  ",
           ( found ) ? ( start + 1 ) : 0,
           ( ( start + MAX_ADMIN_SHOWBANS ) > found ) ?
           found : ( start + MAX_ADMIN_SHOWBANS ),
           found ) );
  if( ( start + MAX_ADMIN_SHOWBANS ) < found )
  {
    ADMBP( va( "run !showbans %d to see more",
             ( start + MAX_ADMIN_SHOWBANS + 1 ) ) );
  }
  ADMBP( "\n" );
  ADMBP_end();
  return qtrue;
}

qboolean G_admin_showentities( gentity_t *ent, int skiparg )
{
	int i;
	gentity_t* cur_ent;
	
	Com_Printf("==========Showing Entities==========\n");
	for(i = 0; i < level.num_entities; i++) {
		cur_ent = &g_entities[ i ];
		if(cur_ent->s.modelindex == ET_PLAYER)
			Com_Printf("%d: %d\n", i, cur_ent->s.modelindex);
	}
	Com_Printf("======Done=====\n");
	return qtrue;
	
}

qboolean G_admin_help( gentity_t *ent, int skiparg )
{
  int i;

  if( G_SayArgc() < 2 + skiparg )
  {
    int j = 0;
    int count = 0;

    ADMBP_begin();
    ADMBP( "^3\n----------------------------^7\n" );
    ADMBP( va( "Welcome to admin help where you can find help with available admin commands. For your convenience, only commands that are available to you based on your admin level will be shown. There are ^3%i^7 commands:\n", count ) );
    for( i = 0; i < g_admin_number_cmds; i++ )
    {
      if( G_admin_cmd_permission( ent, &g_admin_cmds[ i ] ) )
      {
        ADMBP( va( "^3!%-12s", g_admin_cmds[ i ].keyword ) );
        j++;
        count++;
      }
      // show 6 commands per line
      if( j == 6 )
      {
    	  ADMBP( "\n" );
        j = 0;
      }
    }
    for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_scripts[ i ]; i++ )
    {
      if( ! G_admin_script_permission( ent, g_admin_scripts[ i ] ) )
        continue;
      ADMBP( va( "^3!%-12s", g_admin_scripts[ i ]->command_name ) );
      j++;
      count++;
      // show 6 commands per line
      if( j == 6 )
      {
    	  ADMBP( "\n" );
        j = 0;
      }
    }
    if( count )
	ADMBP( "\n^7To get information on a specific command type:\n^3!help [command]^7\n" );
    ADMBP( "^3-----------------------^7\n" );
    ADMBP_end();

    return qtrue;
  }
  else
  {
    //!help param
    char param[ MAX_ADMIN_CMD_LEN ];
    char *cmd;

    G_SayArgv( 1 + skiparg, param, sizeof( param ) );
    cmd = ( param[0] == '!' ) ? &param[1] : &param[0];
    ADMBP_begin();
    for( i = 0; i < g_admin_number_cmds; i++ )
    {
      if( !Q_stricmp( cmd, g_admin_cmds[ i ].keyword ) )
      {
        if( !G_admin_cmd_permission( ent, &g_admin_cmds[ i ] ) )
        {
          ADMBP( va( "^3!help: ^7you have no permission to use '%s'\n",
                   g_admin_cmds[ i ].keyword ) );
          ADMBP_end();
          return qfalse;
        }
        ADMBP( va( "^3!help: ^7help for '!%s':\n",
          g_admin_cmds[ i ].keyword ) );
        ADMBP( va( " ^3Function: ^7%s\n", g_admin_cmds[ i ].function ) );
        ADMBP( va( " ^3Syntax: ^7!%s %s\n", g_admin_cmds[ i ].keyword,
                 g_admin_cmds[ i ].syntax ) );
        ADMBP( va( " ^3Group: ^7'%c'\n", g_admin_cmds[ i ].group[ 0 ] ) );
        ADMBP_end();
        return qtrue;
      }
    }
    for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_scripts[ i ]; i++ )
    {
      if( !Q_stricmp( cmd, g_admin_scripts[ i ]->command_name ) )
      {
        if( !G_admin_script_permission( ent, g_admin_scripts[ i ] ) )
        {
          ADMBP( va( "^3!help: ^7you have no permission to use '%s'\n",
                   g_admin_scripts[ i ]->command_name ) );
          ADMBP_end();
          return qfalse;
        }
        ADMBP( va( "^3!help: ^7help for '%s':\n",
          g_admin_scripts[ i ]->command_name ) );
        ADMBP( va( " ^3Description: ^7%s\n", g_admin_scripts[ i ]->desc ) );
        ADMBP( va( " ^3Syntax: ^7!%s\n", g_admin_scripts[ i ]->command_name ) );
        ADMBP_end();
        return qtrue;
      }
    }
    ADMBP( va( "^3!help: ^7no help found for '%s'\n", cmd ) );
    ADMBP_end();
    return qfalse;
  }
}

qboolean G_admin_info( gentity_t *ent, int skiparg )
{
  int length;
  char filename[ MAX_OSPATH ], infoname[32]; 
  char info[ MAX_STRING_CHARS ], message[ MAX_STRING_CHARS ];
  fileHandle_t handle;
  if( G_SayArgc() < 2 + skiparg ) //if no subject use default
    Q_strncpyz( infoname, "default", 8 );
  else //what subject?
    G_SayArgv( 1 + skiparg, infoname, sizeof( infoname ) );
  Com_sprintf( filename, MAX_OSPATH, "info/info-%s.txt", infoname );
  length = trap_FS_FOpenFile( filename, &handle, FS_READ );
  if( !handle || !length ) //file not found or length 0
  {
    if( Q_stricmp( infoname, "default" ) )
      ADMP( va( "^3!info: %s not found, using default\n", infoname ) );
    length = trap_FS_FOpenFile( "info/info-default.txt", &handle, FS_READ );
  }
  if( handle && length ) //we have something printable
  {
    if( length > MAX_STRING_CHARS ) length = MAX_STRING_CHARS;
    trap_FS_Read( message, length, handle );
    trap_FS_FCloseFile( handle );
    message[length] = '\0';
    G_admin_info_parse( message, info, MAX_STRING_CHARS );
    ADMP( va( "%s\n", info ) );
  } 
  else
  {
    ADMP( "^3!info: no server info found\n" );
    return qfalse;
  } //FAIL
  return qtrue;
}

void G_admin_info_parse( char *unparsed, char *dest, int maxlength )
{ //get unparsed, turn ${cvar} into value and do some escapes, 
  //write maxlength chars to dest (including null-termination),
  //so only maxlength - 1 of useful stuff
  //not necessarily just for !info, needs more general name (and location)
  //do _not_ call this function with src == dest
  char cvarName[ 64 ], cvarValue[ MAX_CVAR_VALUE_STRING ];
  char parsedValue[ MAX_CVAR_VALUE_STRING ];
  int i, j, cursor = 0; //i is our position in dest, cursor in unparsed
  for( i = 0; cursor <= strlen( unparsed ) && i < maxlength; i++ ) 
  { //until we run out of space or unparsed
    if( unparsed[cursor] == '\\' )
    { //some escaping
      if( unparsed[cursor + 1] == 'n' )
      {
	dest[i] = '\n';
        cursor++; //skip over this next loop
      }
      else if( unparsed[cursor + 1] == '\\' )
      {
	dest[i] = '\\';
	cursor++; //skip me
      }
      else if( unparsed[cursor + 1] == '$' )
      {
	dest[i] = '$';
	cursor++; //skip me
      }
    }
    else if( unparsed[cursor] == '$' )
    { //read variable
      if( unparsed[1+cursor++] == '{' ) cursor++; // skip $ and { if there
      for( j = 0; j < 64 && unparsed[cursor] != '}' && 
	   unparsed[cursor] != ' '; j++ )
      { //read into cvarName until } or space
	cvarName[ j ] = unparsed[cursor++];
      }
      cvarName[ j ] = '\0'; //YOU ARE TERMINATED
      trap_Cvar_VariableStringBuffer( cvarName, cvarValue, 
	                              MAX_CVAR_VALUE_STRING );
      //erk recursion, possible high CPU use?
      G_admin_info_parse( cvarValue, parsedValue, MAX_STRING_CHARS ); 
      Com_sprintf( dest, maxlength, "%s%s", dest, parsedValue ); 
      i += strlen( parsedValue ) - 1; //-1 so we overwrite the null-termination
    }
    else
      dest[i] = unparsed[cursor]; //nothing special so just copy it
    cursor++; //move to the next unparsed
  }
  dest[ maxlength ] = '\0'; //I NEED YOUR CLOTHES YOUR BOOTS AND YOUR MOTORCYCLE
}

qboolean G_admin_admintest( gentity_t *ent, int skiparg )
{
  int i, l = 0;
  qboolean found = qfalse;
  qboolean lname = qfalse;

  if( !ent )
  {
    ADMP( "^3!admintest: ^7you are on the console.\n" );
    return qtrue;
  }
  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
  {
    if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
    {
      found = qtrue;
      break;
    }
  }

  if( found )
  {
    l = g_admin_admins[ i ]->level;
    for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
    {
      if( g_admin_levels[ i ]->level != l )
        continue;
      if( *g_admin_levels[ i ]->name )
      {
        lname = qtrue;
        break;
      }
    }
  }
  AP( va( "print \"^3!admintest: ^7%s^7 is a level %d admin %s%s^7%s\n\"",
          ent->client->pers.netname,
          l,
          ( lname ) ? "(" : "",
          ( lname ) ? g_admin_levels[ i ]->name : "",
          ( lname ) ? ")" : "" ) );
  return qtrue;
}

qboolean G_admin_allready( gentity_t *ent, int skiparg )
{
  int i = 0;
  gclient_t *cl;

  if( !level.intermissiontime )
  {
    ADMP( "^3!allready: ^7this command is only valid during intermission\n" );
    return qfalse;
  }

  for( i = 0; i < g_maxclients.integer; i++ )
  {
    cl = level.clients + i;
    if( cl->pers.connected != CON_CONNECTED )
      continue;

    if( cl->pers.teamSelection == PTE_NONE )
      continue;

    cl->readyToExit = 1;
  }
  AP( va( "print \"^3!allready:^7 %s^7 says everyone is READY now\n\"",
     ( ent ) ? ent->client->pers.netname : "console" ) ); 
  return qtrue;
}

qboolean G_admin_cancelvote( gentity_t *ent, int skiparg )
{

  if(!level.voteTime && !level.teamVoteTime[ 0 ] && !level.teamVoteTime[ 1 ] )
  {
    ADMP( "^3!cancelvote^7: no vote in progress\n" );
    return qfalse;
  }
  level.voteNo = level.numConnectedClients;
  level.voteYes = 0;
  CheckVote( );
  level.teamVoteNo[ 0 ] = level.numConnectedClients;
  level.teamVoteYes[ 0 ] = 0;
  CheckTeamVote( PTE_HUMANS );
  level.teamVoteNo[ 1 ] = level.numConnectedClients;
  level.teamVoteYes[ 1 ] = 0;
  CheckTeamVote( PTE_ALIENS );
  AP( va( "print \"^3!cancelvote: ^7%s^7 decided that everyone voted No\n\"",
          ( ent ) ? ent->client->pers.netname : "console" ) );
  return qtrue;
}

qboolean G_admin_passvote( gentity_t *ent, int skiparg )
{
  if(!level.voteTime && !level.teamVoteTime[ 0 ] && !level.teamVoteTime[ 1 ] )
  {
    ADMP( "^3!passvote^7: no vote in progress\n" );
    return qfalse;
  }
  level.voteYes = level.numConnectedClients;
  level.voteNo = 0;
  CheckVote( );
  level.teamVoteYes[ 0 ] = level.numConnectedClients;
  level.teamVoteNo[ 0 ] = 0;
  CheckTeamVote( PTE_HUMANS );
  level.teamVoteYes[ 1 ] = level.numConnectedClients;
  level.teamVoteNo[ 1 ] = 0;
  CheckTeamVote( PTE_ALIENS );
  AP( va( "print \"^3!passvote: ^7%s^7 decided that everyone voted Yes\n\"",
          ( ent ) ? ent->client->pers.netname : "console" ) );
  return qtrue;
}

qboolean G_admin_spec999( gentity_t *ent, int skiparg )
{
  int i;
  gentity_t *vic;

  for( i = 0; i < level.maxclients; i++ )
  {
    vic = &g_entities[ i ];
    if( !vic->client )
      continue;
    if( vic->client->pers.connected != CON_CONNECTED )
      continue;
    if( vic->client->pers.teamSelection == PTE_NONE )
      continue;
    if( vic->client->ps.ping == 999 )
    {
      G_ChangeTeam( vic, PTE_NONE );
      AP( va( "print \"^3!spec999: ^7%s^7 moved ^7%s^7 to spectators\n\"",
        ( ent ) ? ent->client->pers.netname : "console", 
        vic->client->pers.netname ) );
    }
  }
  return qtrue;
}

qboolean G_admin_register(gentity_t *ent, int skiparg ){
  int level = 0;

  level = G_admin_level(ent);
	
  if( level == 0 )
   level = 1;
  
  if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) )
  {
    ADMP( va( "^3!register: ^7 You cannot register for name protection until you update your client. Please replace your client executable with the one at http://trem.tjw.org/backport/ and reconnect. Updating your client will also allow you to have faster map downloads.\n" ) );
    return qfalse;
  }

  trap_SendConsoleCommand( EXEC_APPEND,va( "!setlevel %d %d;",ent - g_entities, level) );
  ClientUserinfoChanged( ent - g_entities );
  
  AP( va( "print \"^3!register: ^7%s^7 is now a protected nickname.\n\"", ent->client->pers.netname) );
  
  return qtrue;
}

qboolean G_admin_rename( gentity_t *ent, int skiparg )
{
  int pids[ MAX_CLIENTS ];
  char name[ MAX_NAME_LENGTH ];
  char newname[ MAX_NAME_LENGTH ];
  char oldname[ MAX_NAME_LENGTH ];
  char err[ MAX_STRING_CHARS ];
  char userinfo[ MAX_INFO_STRING ];
  char *s;
  gentity_t *victim = NULL;

  if( G_SayArgc() < 3 + skiparg )
  {
    ADMP( "^3!rename: ^7usage: !rename [name] [newname]\n" );
    return qfalse;
  }
  G_SayArgv( 1 + skiparg, name, sizeof( name ) );
  s = G_SayConcatArgs( 2 + skiparg );
  Q_strncpyz( newname, s, sizeof( newname ) );
  if( G_ClientNumbersFromString( name, pids ) != 1 )
  {
    G_MatchOnePlayer( pids, err, sizeof( err ) );
    ADMP( va( "^3!rename: ^7%s\n", err ) );
    return qfalse;
  }
  victim = &g_entities[ pids[ 0 ] ] ;
  if( !admin_higher( ent, victim ) )
  {
    ADMP( "^3!rename: ^7sorry, but your intended victim has a higher admin"
        " level than you\n" );
    return qfalse;
  }
  if( !G_admin_name_check( victim, newname, err, sizeof( err ) ) )
  {
    ADMP( va( "^3!rename: ^7%s\n", err ) );
    return qfalse;
  }
  level.clients[ pids[ 0 ] ].pers.nameChanges--;
  level.clients[ pids[ 0 ] ].pers.nameChangeTime = 0;
  trap_GetUserinfo( pids[ 0 ], userinfo, sizeof( userinfo ) );
  s = Info_ValueForKey( userinfo, "name" );
  Q_strncpyz( oldname, s, sizeof( oldname ) );
  Info_SetValueForKey( userinfo, "name", newname );
  trap_SetUserinfo( pids[ 0 ], userinfo );
  ClientUserinfoChanged( pids[ 0 ] );
  if( strcmp( oldname, level.clients[ pids[ 0 ] ].pers.netname ) )
    AP( va( "print \"^3!rename: ^7%s^7 has been renamed to %s^7 by %s\n\"",
	    oldname,
	    level.clients[ pids[ 0 ] ].pers.netname,
	    ( ent ) ? ent->client->pers.netname : "console" ) );
  return qtrue;
}

qboolean G_admin_restart( gentity_t *ent, int skiparg )
{
  char layout[ MAX_CVAR_VALUE_STRING ] = { "" };
  char teampref[ MAX_CVAR_VALUE_STRING ] = { "" };
  int i;
  gclient_t *cl;

  if( G_SayArgc( ) > 1 + skiparg )
  {
    char map[ MAX_QPATH ];

    trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
    G_SayArgv( skiparg + 1, layout, sizeof( layout ) );

    if( Q_stricmp( layout, "keepteams" ) && Q_stricmp( layout, "keepteamslock" ) && Q_stricmp( layout, "switchteams" ) && Q_stricmp( layout, "switchteamslock" ) )
    {
      if( !Q_stricmp( layout, "*BUILTIN*" ) ||
        trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ),
          NULL, FS_READ ) > 0 )
      {
        trap_Cvar_Set( "g_layouts", layout );
      }
      else
      {
        ADMP( va( "^3!restart: ^7layout '%s' does not exist\n", layout ) );
        return qfalse;
      }
    }
    else 
    {
      strcpy(layout,"");
      G_SayArgv( skiparg + 1, teampref, sizeof( teampref ) );    
    }
  }
  
  if( G_SayArgc( ) > 2 + skiparg ) 
  {
    G_SayArgv( skiparg + 2, teampref, sizeof( teampref ) );      
  }
  
  
  if( !Q_stricmp( teampref, "keepteams" ) || !Q_stricmp( teampref, "keepteamslock" ) )
  {
    for( i = 0; i < g_maxclients.integer; i++ )
    {
      cl = level.clients + i;
      if( cl->pers.connected != CON_CONNECTED )
        continue;

      if( cl->pers.teamSelection == PTE_NONE )
        continue;

      cl->sess.restartTeam = cl->pers.teamSelection;
    }
  } 
  else if(!Q_stricmp( teampref, "switchteams" ) ||  !Q_stricmp( teampref, "switchteamslock" ))
  {
    for( i = 0; i < g_maxclients.integer; i++ )
    {
      cl = level.clients + i;
      if( cl->pers.connected != CON_CONNECTED )
        continue;

      if( cl->pers.teamSelection == PTE_NONE )
        continue;

      if( cl->pers.teamSelection == PTE_ALIENS )
        cl->sess.restartTeam = PTE_HUMANS;
      else if(cl->pers.teamSelection == PTE_HUMANS )
    	  cl->sess.restartTeam = PTE_ALIENS;
    }  	  
  }
  
  if( !Q_stricmp( teampref, "switchteamslock" ) || !Q_stricmp( teampref, "keepteamslock" ) )
  {
    trap_Cvar_Set( "g_lockTeamsAtStart", "1" );
  }
  level.numBots = 0;
  trap_SendConsoleCommand( EXEC_APPEND, "map_restart" );
  
  if(teampref[ 0 ])
    strcpy(teampref,va( "^7(with teams option: '%s^7')", teampref ));
  
  AP( va( "print \"^3!restart: ^7map restarted by %s %s %s\n\"",
          ( ent ) ? ent->client->pers.netname : "console",
          ( layout[ 0 ] ) ? va( "^7(forcing layout '%s^7')", layout ) : "",
          teampref ) );
  return qtrue;
}

qboolean G_admin_nextmap( gentity_t *ent, int skiparg )
{
  AP( va( "print \"^3!nextmap: ^7%s^7 decided to load the next map\n\"",
    ( ent ) ? ent->client->pers.netname : "console" ) );
  level.lastWin = PTE_NONE;
  trap_SetConfigstring( CS_WINNER, "Evacuation" );
  LogExit( va( "nextmap was run by %s",
    ( ent ) ? ent->client->pers.netname : "console" ) );
  return qtrue;
}

qboolean G_admin_namelog( gentity_t *ent, int skiparg )
{
  int i, j;
  char search[ MAX_NAME_LENGTH ] = {""};
  char s2[ MAX_NAME_LENGTH ] = {""};
  char n2[ MAX_NAME_LENGTH ] = {""};
  char guid_stub[ 9 ];
  qboolean found = qfalse;
  int printed = 0;

  if( G_SayArgc() > 1 + skiparg )
  {
    G_SayArgv( 1 + skiparg, search, sizeof( search ) );
    G_SanitiseName( search, s2 );
  }
  ADMBP_begin();
  for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
  {
    if( search[0] )
    {
      found = qfalse;
      for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && 
        g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
      {
        G_SanitiseName( g_admin_namelog[ i ]->name[ j ], n2 );
        if( strstr( n2, s2 ) )
        {
          found = qtrue;
          break;
        }
      }
      if( !found )
        continue;
    }
    printed++;
    for( j = 0; j <= 8; j++ )
      guid_stub[ j ] = g_admin_namelog[ i ]->guid[ j + 24 ];
    guid_stub[ j ] = '\0';
    if( g_admin_namelog[ i ]->slot > -1 )
       ADMBP( "^3" );
    ADMBP( va( "%-2s (*%s) %15s^7", 
      (g_admin_namelog[ i ]->slot > -1 ) ?
        va( "%d", g_admin_namelog[ i ]->slot ) : "-",
      guid_stub, g_admin_namelog[ i ]->ip ) );
    for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && 
      g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
    {
      ADMBP( va( " '%s^7'", g_admin_namelog[ i ]->name[ j ] ) );
    }
    ADMBP( "\n" ); 
  } 
  ADMBP( va( "^3!namelog:^7 %d recent clients found\n", printed ) );
  ADMBP_end();
  return qtrue;
}

qboolean G_admin_lock( gentity_t *ent, int skiparg )
{
  char teamName[2] = {""};
  pTeam_t team;

  if( G_SayArgc() < 2 + skiparg )
  {
    ADMP( "^3!lock: ^7usage: !lock [a|h]\n" );
    return qfalse;
  }
  G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) );
  if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' )
    team = PTE_ALIENS;
  else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' )
    team = PTE_HUMANS;
  else
  {
    ADMP( va( "^3!lock: ^7invalid team\"%c\"\n", teamName[0] ) );
    return qfalse;
  }

  if( team == PTE_ALIENS )
  {
    if( level.alienTeamLocked )
    {
      ADMP( "^3!lock: ^7Alien team is already locked\n" );
      return qfalse;
    }
    else
      level.alienTeamLocked = qtrue;
  }
  else if( team == PTE_HUMANS ) {
    if( level.humanTeamLocked )
    {
      ADMP( "^3!lock: ^7Human team is already locked\n" );
      return qfalse;
    }
    else
      level.humanTeamLocked = qtrue;
  }

  AP( va( "print \"^3!lock: ^7%s team has been locked by %s\n\"",
    ( team == PTE_ALIENS ) ? "Alien" : "Human",
    ( ent ) ? ent->client->pers.netname : "console" ) );
  return qtrue;
} 

qboolean G_admin_unlock( gentity_t *ent, int skiparg )
{
  char teamName[2] = {""};
  pTeam_t team;

  if( G_SayArgc() < 2 + skiparg )
  {
    ADMP( "^3!unlock: ^7usage: !unlock [a|h]\n" );
    return qfalse;
  }
  G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) );
  if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' )
    team = PTE_ALIENS;
  else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' )
    team = PTE_HUMANS;
  else
  {
    ADMP( va( "^3!unlock: ^7invalid team\"%c\"\n", teamName[0] ) );
    return qfalse;
  }
  
  if( team == PTE_ALIENS )
  {
    if( !level.alienTeamLocked )
    {
      ADMP( "^3!unlock: ^7Alien team is not currently locked\n" );
      return qfalse;
    }
    else
      level.alienTeamLocked = qfalse;
  }
  else if( team == PTE_HUMANS ) {
    if( !level.humanTeamLocked )
    {
      ADMP( "^3!unlock: ^7Human team is not currently locked\n" );
      return qfalse;
    }
    else
      level.humanTeamLocked = qfalse;
  }

  AP( va( "print \"^3!unlock: ^7%s team has been unlocked by %s\n\"",
    ( team == PTE_ALIENS ) ? "Alien" : "Human",
    ( ent ) ? ent->client->pers.netname : "console" ) );
  return qtrue;
} 

qboolean G_admin_designate( gentity_t *ent, int skiparg )
{
  int pids[ MAX_CLIENTS ];
  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
  char command[ MAX_ADMIN_CMD_LEN ], *cmd;
  gentity_t *vic;

  if( G_SayArgc() < 2 + skiparg )
  {
    ADMP( "^3!designate: ^7usage: designate [name|slot#]\n" );
    return qfalse;
  }
  G_SayArgv( skiparg, command, sizeof( command ) );
  cmd = command;
  if( cmd && *cmd == '!' )
    cmd++;
  G_SayArgv( 1 + skiparg, name, sizeof( name ) );
  if( G_ClientNumbersFromString( name, pids ) != 1 )
  {
    G_MatchOnePlayer( pids, err, sizeof( err ) );
    ADMP( va( "^3!designate: ^7%s\n", err ) );
    return qfalse;
  }
  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) &&
    !Q_stricmp( cmd, "undesignate" ) )
  {
    ADMP( "^3!mute: ^7sorry, but your intended victim has a higher admin"
        " level than you\n" );
    return qfalse;
  }
  vic = &g_entities[ pids[ 0 ] ];
  if( vic->client->pers.designatedBuilder == qtrue )
  {
    if( !Q_stricmp( cmd, "designate" ) )
    {
      ADMP( "^3!designate: ^7player is already designated builder\n" );
      return qtrue;
    }
    vic->client->pers.designatedBuilder = qfalse;
    CPx( pids[ 0 ], "cp \"^1Your designation has been revoked\"" );
    AP( va(
      "print \"^3!designate: ^7%s^7's designation has been revoked by %s\n\"",
       vic->client->pers.netname,
       ( ent ) ? ent->client->pers.netname : "console" ) );
    G_CheckDBProtection( );
  }
  else
  {
    if( !Q_stricmp( cmd, "undesignate" ) )
    {
      ADMP( "^3!undesignate: ^7player is not currently designated builder\n" );
      return qtrue;
    }
    vic->client->pers.designatedBuilder = qtrue;
    CPx( pids[ 0 ], "cp \"^1You've been designated\"" );
    AP( va( "print \"^3!designate: ^7%s^7 has been designated by ^7%s\n\"",
      vic->client->pers.netname,
      ( ent ) ? ent->client->pers.netname : "console" ) );
  }
  ClientUserinfoChanged( pids[ 0 ] );
  return qtrue;
}

//Begin Ender code
qboolean G_admin_give(gentity_t *ent, int skiparg) {
	char item[ MAX_SAY_TEXT ]; //passed in item/amount to give
	char name[ MAX_NAME_LENGTH ]; //passed in name to give to
	int credits; //parsed amount to give
	int pids[ MAX_CLIENTS ]; //array of player ids that match the name string
	gentity_t *target_player; //the player to receive the funds
	char s2[ MAX_NAME_LENGTH ]; //used for sanitizing the name
	char err[ MAX_STRING_CHARS ]; //used to get the error for when we check that give only gives to one player
	int i;
	
	//check arguments
	if (G_SayArgc() < 3 + skiparg ) {
		ADMP( "^3!give: ^7usage: !give [name] [item/funds amount]\n" );
		return qfalse;
	}

	//parse arguments
	G_SayArgv( 1 + skiparg, name, sizeof(name));
	G_SanitiseName( name, s2 );
	G_SayArgv( 2 + skiparg, item, sizeof(item));
	credits = -1;
	credits = atoi( item );
	//credits will be equal to 0 iff item was equal to a string, or "0"
	if(credits == -1) //no item specified
	{
		Com_Printf("Error: Give - shouldn't be able to do that\n");
		return qfalse;
	}
	if(!Q_stricmp(item, "0")) //user specified '0' for the item
	{
		ADMP( "Stop trying to give nothing, it's philosophical, but not useful.\n" );
		return qfalse;
	}
	if(credits > 0) //user gave a valid number for the item
		credits = credits > HUMAN_MAX_CREDITS ? HUMAN_MAX_CREDITS : credits; //cap the number

	//check that the name is valid and matches just one name
	if (G_ClientNumbersFromString(name, pids ) != 1) {
		G_MatchOnePlayer(pids, err, sizeof(err));
		ADMP( va( "^3!give: ^7%s\n", err ) );
		return qfalse;
	}
	//at this point, pids should contain an array of player IDs that matched the string
	//so get the exact entity to give funds to from the player IDs
	target_player = &g_entities[ pids[ 0 ] ];
    
	//give the credits
	if(credits > 0 ) {
		G_AddCreditToClient( target_player->client, credits, qtrue );
		
	
		//tell everyone what happened
		AP( va( "print \"^3!give: ^7%s^7 gave %s^7 %d %s\n\"",
			( ent ) ? ent->client->pers.netname : "console",
			target_player->client->pers.netname,
			credits,
			(target_player->client->pers.teamSelection == PTE_HUMANS) ? "credits" : "evos" ) );
		return qtrue;
	} else { //give the item
		int weapon, upgrade, cur_weapon;
		weapon = BG_FindWeaponNumForName( item );
		upgrade = BG_FindUpgradeNumForName( item );

		
		if( weapon != WP_NONE ) { //giving a weapon
			int maxAmmo, maxClips;
			//already got this?
			if( BG_InventoryContainsWeapon( weapon, target_player->client->ps.stats ) )
			{
				ADMP( va( "^3!give: ^7%s already has a %s\n", target_player->client->pers.netname, item ) );
				return qfalse;;
			}
			//on the correct team?
			if( target_player->client->pers.teamSelection != PTE_HUMANS )
			{
				ADMP( va( "^3!give: ^7%s is not on the right team for a %s\n", target_player->client->pers.netname, item ) );
				return qfalse;
			}
			//weapon allowed?
			if( !BG_FindPurchasableForWeapon( weapon ) )
			{
				ADMP( va( "^3!give:^7 '%s' is not allowed at this time\n", item) );
				return qfalse;
			}
			//are we on the right stage to have this?
			if( !BG_FindStagesForWeapon( weapon, g_humanStage.integer ) || !BG_WeaponIsAllowed( weapon ) )
			{
				ADMP( va( "^3!give:^7 '%s' is not allowed at this stage\n", item) );
				return qfalse;
			}
			//get the player's current weapon
			cur_weapon = WP_NONE;
			for( i = WP_MACHINEGUN; i < WP_LUCIFER_CANNON; i++) {
				if( BG_InventoryContainsWeapon( i, target_player->client->ps.stats ) ) {
					cur_weapon = i;
					i = WP_NUM_WEAPONS;
					Com_Printf("Player is holding weapon #%d\n", cur_weapon);
				}
			}
			//remove the current weapon from the inventory
			if(cur_weapon >= WP_MACHINEGUN && cur_weapon <= WP_LUCIFER_CANNON)
				BG_RemoveWeaponFromInventory( cur_weapon, target_player->client->ps.stats );
			else
				Com_Printf("Player is not holding a weapon. cur_weapon was %d\n", cur_weapon);
			//add to inventory
			BG_AddWeaponToInventory( weapon, target_player->client->ps.stats );
		    BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips );
		    
		    if( BG_FindUsesEnergyForWeapon( weapon ) &&
		        BG_InventoryContainsUpgrade( UP_BATTPACK, target_player->client->ps.stats ) )
		      maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER );

		    BG_PackAmmoArray( weapon, target_player->client->ps.ammo, target_player->client->ps.powerups,
		                      maxAmmo, maxClips );

		    G_ForceWeaponChange( target_player, weapon );

		    //set build delay/pounce etc to 0
			target_player->client->ps.stats[ STAT_MISC ] = 0;
		}
		if( upgrade != UP_NONE ) { //giving an upgrade
			qboolean  givingEnergyAmmo = qfalse;
			qboolean  hasEnergyWeapon = qfalse;
					
			//already got this?
			if( BG_InventoryContainsUpgrade( upgrade, target_player->client->ps.stats ) )
			{
				ADMP( va( "^3!give: ^7%s already has a %s\n", target_player->client->pers.netname, item ) );
				return qfalse;
			}
			if( BG_FindSlotsForUpgrade( upgrade ) & target_player->client->ps.stats[ STAT_SLOTS ] ) 
			{
				ADMP( va( "^3!give: ^7%s has no room for a %s\n", target_player->client->pers.netname, item ) );
				return qfalse;
			}
			//on the correct team?
			if( target_player->client->pers.teamSelection != PTE_HUMANS )
			{
				ADMP( va( "^3!give: ^7%s is not on the right team for a %s\n", target_player->client->pers.netname, item ) );
				return qfalse;
			}
			//are we /allowed/ to have this?
			if( !BG_FindPurchasableForUpgrade( upgrade ) )
			{
				ADMP( va( "^3!give: ^7%s can't have %s at this time\n", target_player->client->pers.netname, item ) );
				return;
			}

			//are we at the right stage to have this?
			if( !BG_FindStagesForUpgrade( upgrade, g_humanStage.integer ) || !BG_UpgradeIsAllowed( upgrade ) )
			{
				ADMP( va( "^3!give: ^7%s can't have %s at this stage\n", target_player->client->pers.netname, item ) );
				return;
			}

			if( upgrade == UP_AMMO ) {
				for( i = WP_NONE; i < WP_NUM_WEAPONS; i++ )
				{
					if( BG_InventoryContainsWeapon( i, target_player->client->ps.stats ) )
					{
						if( BG_FindUsesEnergyForWeapon( i ) )
						hasEnergyWeapon = qtrue;
					}
				}
				if( weapon == WP_NONE && upgrade == UP_AMMO )
				{
					givingEnergyAmmo = hasEnergyWeapon;
				}

				G_GiveClientMaxAmmo( target_player, givingEnergyAmmo );
			}
			else
			{
				//add to inventory
				BG_AddUpgradeToInventory( upgrade, ent->client->ps.stats );
			}

			if( upgrade == UP_BATTPACK )
			G_GiveClientMaxAmmo( ent, qtrue );

		}

		else if( !Q_stricmp(item, "stamina") ) {
			target_player->client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
		}
		else if( !Q_stricmp(item, "poison" ) ) {
			target_player->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;
			target_player->client->lastBoostedTime = level.time;
		}
		
		AP( va( "print \"^3!give: ^7%s^7 gave %s^7 a %s\n\"",
			( ent ) ? ent->client->pers.netname : "console",
			target_player->client->pers.netname,
			item ) );
		ClientUserinfoChanged( target_player->client->ps.clientNum );
		return qtrue;
	}

}

qboolean G_admin_take( gentity_t *ent, int skiparg ) {
	char amount[ 7 ]; //passed in amount to give
	char name[ MAX_NAME_LENGTH ]; //passed in name to give to
	int credits; //parsed amount to give
	int pids[ MAX_CLIENTS ]; //array of player ids that match the name string
	gentity_t *target_player; //the player to receive the funds
	char s2[ MAX_NAME_LENGTH ]; //used for sanitizing the name
	char err[ MAX_STRING_CHARS ]; //used to get the error for when we check that give only gives to one player
	
	//check arguments
	if (G_SayArgc() < 3 + skiparg ) {
		ADMP( "^3!take: ^7usage: !give [name] [amount]\n" );
		return qfalse;
	}

	//parse arguments
	G_SayArgv( 1 + skiparg, name, sizeof(name));
	G_SanitiseName( name, s2 );
	G_SayArgv( 2 + skiparg, amount, sizeof(amount));
	credits = atoi( amount );
	credits = credits > HUMAN_MAX_CREDITS ? HUMAN_MAX_CREDITS : credits;

	//check that the name is valid and matches just one name
	if (G_ClientNumbersFromString(name, pids ) != 1) {
		G_MatchOnePlayer(pids, err, sizeof(err));
		ADMP( va( "^3!take: ^7%s\n", err ) );
		return qfalse;
	}
	//at this point, pids should contain an array of player IDs that matched the string
	//so get the exact entity to give funds to from the player IDs
	target_player = &g_entities[ pids[ 0 ] ];
    
	//give the credits
	G_TakeCreditFromClient( target_player->client, credits );

	//tell everyone what happened
	AP( va( "print \"^3!take: ^7%s^7 took %d %s from %s^7\n\"",
		( ent ) ? ent->client->pers.netname : "console",
		credits,
		(target_player->client->pers.teamSelection == PTE_HUMANS) ? "credits" : "evos",
		target_player->client->pers.netname ) );
	return qtrue;
	
}

qboolean G_admin_toggleinstantbuild( gentity_t *ent, int skiparg )
{
	int cur_val = trap_Cvar_VariableIntegerValue("g_instantBuild");
	if(cur_val) {
		trap_Cvar_Set( "g_instantBuild", "0" );
		AP( va( "print \"^3!toggleInstantBuild:^7 %s^7 disabled instant building\n\"",
			( ent ) ? ent->client->pers.netname : "console" ) );
			
	} else {
		trap_Cvar_Set( "g_instantBuild", "1" );
		AP( va( "print \"^3!toggleInstantBuild:^7 %s^7 enabled instant building\n\"",
			( ent ) ? ent->client->pers.netname : "console" ) );
	}
	return qtrue;
}

qboolean G_admin_rcon( gentity_t *ent, int skiparg )
{
	trap_SendServerCommand( ent-g_entities,
	         va( "print \"I'm sure you really meant to use /rcon\n\"" ) );
	return qtrue;
}

qboolean G_admin_clinic( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!clinic: ^7The next clinic has not yet been decided - go to ^3http://www.dretchstorm.com/node/2773^7 to vote for when you'd like clinic times to be!\n\"" ) );//console announcement
	return qtrue;
}

qboolean G_admin_website( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!website: ^7Go to ^3http://www.dretchstorm.com/node/2696^7 for information on this training server!\nHead to http://dretchstorm.com for info on the best Tremulous community out there!\n\"" ) );//console announcement
	return qtrue;
}

qboolean G_admin_mumble( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!mumble: ^7Go to ^3http://www.dretchstorm.com/node/2707^7 for information on our mumbler server - the best way to chat with our trainers/admins/other players!\nBetter than ^1Skype^7 or ^2Teamspeak^7 because it's open source and free!\n\"" ) );//console announcement
	return qtrue;
}

qboolean G_admin_bot( gentity_t *ent, int skiparg ){
		// add [name] (team) (skill)
		// del [name]
		gentity_t *bot;
		int minargc, matches, team_int;
		int pids[ MAX_CLIENTS ];
		char command[10];
		char charname[ MAX_NAME_LENGTH ];
		char			*botinfo;
		char basename[ MAX_NAME_LENGTH ];
		char name[ MAX_NAME_LENGTH ];
		char name_s[ MAX_NAME_LENGTH ];
		//char name2[ MAX_NAME_LENGTH ];
		char name2_s[ MAX_NAME_LENGTH ];
		char team[10];
		//int team_int;
		char skill[2];
		int skill_int=2;
		qboolean success = qfalse;
		int i, j;
		
		//char s2[ MAX_NAME_LENGTH ];
		//char n2[ MAX_NAME_LENGTH ];
		//int logmatch = -1, logmatches = 0;
		//int i, j;
		//qboolean exactmatch = qfalse;
		
		minargc = 3 + skiparg;
		if( G_SayArgc() < skiparg + 1 )	{
			ADMP( "^7Incorrect number of arguments. Use 'bot removeall' to remove all bots, 'bot add [name] [team]' to add a bot, 'bot del [name]' to remove a bot\n" );
			//ADMP( "^3!bot: ^7usage: !bot [add/del] [name] (team) (skill)\n" );
			ADMP( "^3!bot: ^7usage: !bot add [name] [h/a] (character) (skill)\n" );
			return qfalse;
		}
	
		G_SayArgv( 1 + skiparg, command, sizeof( command ) );
		G_SayArgv( 2 + skiparg, name, sizeof( name ) );
		
		if(!Q_stricmp(command,"add")) {
			// add [name] [team]
			//check if we have already hit our limit for bots
			if(level.numBots >= g_maxNumBots.integer ) {
				ADMP( "^7Maximum number of bots are already on the server\n" );
				return qfalse;
			}
			
			
			minargc = 4 + skiparg;
			if( G_SayArgc() < minargc )	{
				ADMP( "^7Please have at least name and team.\n" );
				//ADMP( "^3!bot: ^7usage: !bot [add/del] [name] [humans/aliens] \n" );
				ADMP( "^3!bot: ^7usage: !bot add [name] [h/a] (character) (skill)\n" );
				return qfalse;
			}
			
			G_SayArgv( 3 + skiparg, team, sizeof( team ) );
			
			if(!Q_stricmp(team,"humans")||!Q_stricmp(team,"h")||!Q_stricmp(team,"human")) {
				team_int = PTE_HUMANS;
			} else if(!Q_stricmp(team,"aliens")||!Q_stricmp(team,"a")||!Q_stricmp(team,"alien")) {
				team_int = PTE_ALIENS;
			} else {
				ADMP( "^7Invalid bot team.\n" );
				//ADMP( "^3!bot: ^7usage: !bot add [name] [humans/aliens] (skill)\n" );
				ADMP( "^3!bot: ^7usage: !bot add [name] [h/a] (character) (skill)\n" );
				return qfalse;
			}
			if( team_int == PTE_HUMANS){
				minargc = 5 + skiparg;
				if(G_SayArgc() < minargc) {
					ADMP( "^3!bot add: ^7bot charater not specified using random\n" );
					G_AddRandomBot(ent, team_int, name );
					return qtrue;
				} else {
					G_SayArgv( 4 + skiparg, charname, sizeof( charname ) );
					Com_Printf("Attempting to get info for bot '%s'...\n", charname);
					botinfo = G_GetBotInfoByName( charname );
					if ( !botinfo ) {
						ADMP( va( S_COLOR_RED "Error: Bot character '%s' not defined\n", charname ) );
						return qfalse;
					}
				}
				
				minargc = 5 + skiparg;
				if(G_SayArgc() < minargc) {
					skill_int = 2;
				} else {
					G_SayArgv( 5 + skiparg, skill, sizeof( skill ) );
					skill_int = atoi(skill);
				}
			}
			// got name, team_int and skill_int
			G_BotAdd(ent, name, team_int, skill_int, botinfo);
			return qtrue;
		} else if(!Q_stricmp(command,"del")) {
			minargc = 3 + skiparg;
			if( G_SayArgc() < minargc )	{
				ADMP( "^7Please have at least name\n" );
				//ADMP( "^3!bot: ^7usage: !bot [add/del] [name] [humans/aliens] \n" );
				ADMP( "^3!bot: ^7usage: !bot del [name] (team)\n" );
				return qfalse;
			}
			
			G_SayArgv( 3 + skiparg, team, sizeof( team ) );
			// del [name]
			if(!Q_stricmp(team,"humans")||!Q_stricmp(team,"h")||!Q_stricmp(team,"human")) {
				team_int = PTE_HUMANS;
			} else if(!Q_stricmp(team,"aliens")||!Q_stricmp(team,"a")||!Q_stricmp(team,"alien")) {
				team_int = PTE_ALIENS;
			} else {
				team_int = PTE_NONE;
			}
			success = qfalse;
			matches = G_ClientNumbersFromString( name, pids );
			//G_MatchOnePlayer( pids, err, sizeof( err ) );
			for( i = 0; i <= matches;i++ ) {            
				//if( g_admin_namelog[ pids[ i ] ]->slot >= 0 ) {                
				bot = &g_entities[ pids[ i ] ];
				if( team_int != PTE_NONE){
					if (team_int != bot->client->pers.teamSelection){
					    continue;
					}
				}
	      G_BotDel(ent, pids[ i ]);
				success = qtrue;				
				//}
			}
			
			return success;
	
		} else if( !Q_stricmp( command,"removeall" ) ) {
			G_BotRemoveAll(ent);
			return qtrue;
		}
		
		//ADMP( "^3!bot: ^7usage: !bot [add/del] [name] (team) (skill)\n" );
		ADMP( "^3!bot: ^7usage: !bot [add/del] [name] (team) (charactor) (skill)\n" );
		return qfalse;
}

qboolean G_admin_botlibSetVariable( gentity_t *ent, int skiparg ) {
	char key[MAX_TOKEN_CHARS];
	char value[MAX_TOKEN_CHARS];

	//check arguments
	if (G_SayArgc() < 3 + skiparg ) {
		ADMP( "^3!blibset: ^7usage: !blibset <key> <value>\n" );
		return qfalse;
	}

	//parse arguments
	G_SayArgv( 1 + skiparg, key, sizeof(key));
	G_SayArgv( 2 + skiparg, value, sizeof(value));

	trap_BotLibVarSet(key, va("%s", value ) );
	return qtrue;
}

qboolean G_admin_botcmd( gentity_t *ent, int skiparg ) {
	int minargc;
	char name[ MAX_NAME_LENGTH ];
	char name_s[ MAX_NAME_LENGTH ];
	char name2_s[ MAX_NAME_LENGTH ];
	char command[ 32 ];
	int i, j;
	qboolean success = qfalse;
	
	ADMP( "^3!botcmd: ^7This needs a huge overhaul, don't use it for now\n" );
	return qfalse;
	
	//[botname] [command]
	minargc = 3 + skiparg;
	if( G_SayArgc() < minargc )	{
		ADMP( "^3!botcmd: ^7usage: !botcmd [botname] [command]\n" );
		return qfalse;
	}
	
	G_SayArgv( 1 + skiparg, name, sizeof( name ) );
	G_SayArgv( 2 + skiparg, command, sizeof( command ) );
	G_SanitiseName( name, name_s );
	
	success = qfalse;
	for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ];i++ ) {
		if( g_admin_namelog[ i ]->slot >= 0 ) {
			for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) {
				G_SanitiseName(g_admin_namelog[ i ]->name[ j ], name2_s);
				if( strstr( name2_s, name_s ) ) {
					//G_BotCmd(ent, g_admin_namelog[ i ]->slot,command);
					success = qtrue;
				}
			}
		}
	}
	
	return success;
}


qboolean G_admin_setstage( gentity_t *ent, int skiparg )
{
	char team[MAX_TOKENLENGTH];
	char stage_str[MAX_TOKENLENGTH];
	int stage;
	
	//check arguments
	if (G_SayArgc() < 3 + skiparg ) {
		ADMP( "^3!setstage: ^7usage: !setstage <team> <value>\n" );
		return qfalse;
	}

	//parse arguments
	G_SayArgv( 1 + skiparg, team, sizeof(team));
	G_SayArgv( 2 + skiparg, stage_str, sizeof(stage_str));
	stage = atoi( stage_str );
	if(stage < 1 || stage > 3) {
		ADMP( "^3!setstage: ^7need a valid stage from 1 to 3\n" );
		return qfalse;
	}
	else if(!strcmp(team, "h") || !strcmp(team, "human") || !strcmp(team, "humans")) {
		ADMP( va( "^3!setstage: ^7Set humans to stage %d\n", stage ) );
		trap_Cvar_Set("g_humanStage", va("%d", stage - 1 ) );		
		return qtrue;
	}
	else if(!strcmp(team, "a") || !strcmp(team, "alien") || !strcmp(team, "aliens")) {
		ADMP( va ("^3!setstage: ^7Set aliens to stage %d\n", stage ) );
		trap_Cvar_Set("g_alienStage", va("%d", stage - 1 ) );
		return qtrue;
	}
	else {
		ADMP( "^3!setstage: ^7Need a valid team like 'h', 'human', 'humans', 'a', 'alien', 'aliens'\n");
		return qfalse;
	}
}

qboolean G_admin_setbp( gentity_t *ent, int skiparg )
{
	char team[MAX_TOKENLENGTH];
	char bp_str[MAX_TOKENLENGTH];
	int bp;
	
	//check arguments
	if (G_SayArgc() < 3 + skiparg ) {
		ADMP( "^3!setbp: ^7usage: !setbp <team> <value>\n" );
		return qfalse;
	}

	//parse arguments
	G_SayArgv( 1 + skiparg, team, sizeof(team));
	G_SayArgv( 2 + skiparg, bp_str, sizeof(bp_str));
	bp = atoi(bp_str );
	if(bp < 1 || bp > 400) {
		ADMP( "^3!setbp: ^7need a valid number from 1 to 400\n" );
		return qfalse;
	}
	else if(!strcmp(team, "h") || !strcmp(team, "human") || !strcmp(team, "humans")) {
		ADMP( va( "^3!setbp: ^7Set human build points to %d\n", bp ) );
		trap_Cvar_Set("g_humanbuildpoints", va("%d", bp ) );		
		return qtrue;
	}
	else if(!strcmp(team, "a") || !strcmp(team, "alien") || !strcmp(team, "aliens")) {
		ADMP( va ("^3!setbp: ^7Set alien build points to %d\n", bp ) );
		trap_Cvar_Set("g_alienbuildpoints", va("%d", bp ) );
		return qtrue;
	}
	else {
		ADMP( "^3!setbp: ^7Need a valid team like 'h', 'human', 'humans', 'a', 'alien', 'aliens'\n");
		return qfalse;
	}
}

qboolean G_admin_settime( gentity_t *ent, int skiparg )
{
	char time_str[MAX_TOKENLENGTH];
	int time;
	
	//check arguments
	if (G_SayArgc() < 2 + skiparg ) {
		ADMP( "^3!settime: ^7usage: !settime <time in min>\n" );
		return qfalse;
	}

	//parse arguments
	G_SayArgv( 1 + skiparg, time_str, sizeof(time_str));
	time = atoi( time_str );
	if(time < 0 || time > 60) {
		ADMP( "^3!settime: ^7need a valid time from 0 to 60\n" );
		return qfalse;
	}
	
	//level.time holds the current time in milliseconds
	//level.startTime holds the time in milliseconds when the level started
	//so, we just set the start time to the current time minus the desired time
		
	Com_Printf("Changing time. Level.startTime was %i and will now be %i", level.startTime, level.time - (time * 60000));
	level.startTime = level.time - (time * 60000);
	
	//fix the announcement time first so we aren't flooded with announcements
	level.lastAnnouncementTime = level.time;
	//send the new config string to the clients so they show the new time
	trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) );
	return qtrue;
}

qboolean G_admin_playerstats( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!playerstats:^7 Not yet implemented - Jan 27 2008\n\"" ) );
	return qtrue;
}


qboolean G_admin_xvsall( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!xvsall:^7 Not yet implemented - Jan 27 2008\n\"" ) );
	return qtrue;
}

qboolean G_admin_report( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!xvsall:^7 Not yet implemented - Jan 27 2008\n\"" ) );
	return qtrue;
}

qboolean G_admin_layoutload( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!layoutload:^7 Not yet implemented - Jan 27 2008\n\"" ) );
	return qtrue;
}

qboolean G_admin_layoutdelete( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!layoutdelete:^7 Not yet implemented - Jan 27 2008\n\"" ) );
	return qtrue;
}

qboolean G_admin_normalize( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!normalize:^7 Not yet implemented - Jan 27 2008\n\"" ) );
	return qtrue;
}

qboolean G_admin_gamestats( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!gamestats:^7 Not yet implemented - Jan 27 2008\n\"" ) );
	return qtrue;
}

qboolean G_admin_gamestatus( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!gamestatus:^7 I'm sure you meant ^3'/gamestatus'^7 instead\n\"" ) );
	return qtrue;
}

qboolean G_admin_buildstatus( gentity_t *ent, int skiparg )
{
	AP( va( "print \"^3!buildstatus:^7 I'm sure you meant ^3'/buildstatus'^7 instead.\n\"" ) );
	return qtrue;
}
//End Ender code


 //!Warn by Gate (Daniel Evans) 
qboolean G_admin_warn( gentity_t *ent, int skiparg )
{//mostly copy and paste with the proper lines altered from !mute and !kick
 
 
   int pids[ MAX_CLIENTS ];
   char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];
   int minargc;
   gentity_t *vic;
 
   minargc = 3 + skiparg;
   if( G_admin_flag_permission( ent, ADMF_UNACCOUNTABLE ) )
     minargc = 2 + skiparg;
 
   if( G_SayArgc() < minargc )
   {
     ADMP( "^3!warn: ^7usage: warn [name] [reason]\n" );
     return qfalse;
   }
   G_SayArgv( 1 + skiparg, name, sizeof( name ) );
   reason = G_SayConcatArgs( 2 + skiparg );
   if( G_ClientNumbersFromString( name, pids ) != 1 )
   {
     G_MatchOnePlayer( pids, err, sizeof( err ) );
     ADMP( va( "^3!warn: ^7%s\n", err ) );
     return qfalse;
   }
   if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
   {
     ADMP( "^3!warn: ^7sorry, but your intended victim has a higher admin"
         " level than you.\n" );
     return qfalse;
   }
  
   vic = &g_entities[ pids[ 0 ] ];
   //next line is the onscreen warning
   CPx( pids[ 0 ],va("cp \"^1You have been warned by an administrator.\n ^3Cease imeediately or face admin action!\n^1 %s%s\"",(*reason)? "REASON: " : "" ,(*reason)? reason : "") );
   AP( va( "print \"^3!warn: ^7%s^7 has been warned to cease and desist %s by %s \n\"",
             vic->client->pers.netname, (*reason) ? reason : "his current activity",
             ( ent ) ? ent->client->pers.netname : "console" ) );//console announcement
   ClientUserinfoChanged( pids[ 0 ] );
   return qtrue;
 }
 

qboolean G_admin_putmespec( gentity_t *ent, int skiparg )
{
  if( !ent )
  {
	ADMP( "!specme: sorry, but console isn't allowed on the spectators team\n");
	return qfalse;
  }
  
  if(ent->client->pers.teamSelection == PTE_NONE)
    return qfalse;
  
    //guard against build timer exploit
  if( ent->client->pers.teamSelection != PTE_NONE &&
     ( ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ||
       ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ||
       BG_InventoryContainsWeapon( WP_HBUILD, ent->client->ps.stats ) ||
       BG_InventoryContainsWeapon( WP_HBUILD2, ent->client->ps.stats ) ) &&
      ent->client->ps.stats[ STAT_MISC ] > 0 )
  {
    ADMP("!specme: You cannot leave your team until the build timer expires");
    return qfalse;
  }
  
  G_ChangeTeam( ent, PTE_NONE );
  AP( va("print \"^3!specme: ^7%s^7 decided to join the spectators\n\"", ent->client->pers.netname ) );
  return qtrue;
}

/*
================
 G_admin_print

 This function facilitates the ADMP define.  ADMP() is similar to CP except
 that it prints the message to the server console if ent is not defined.
================
*/
void G_admin_print( gentity_t *ent, char *m )
{

  if( ent )
    trap_SendServerCommand( ent - level.gentities, va( "print \"%s\"", m ) );
  else
  {
    //char m2[ MAX_STRING_CHARS ];
    //G_DecolorString( m, m2 );
    G_Printf( m );
  }
}

void G_admin_buffer_begin()
{
  g_bfb[ 0 ] = '\0';
}

void G_admin_buffer_end( gentity_t *ent )
{
  ADMP( g_bfb );
}

void G_admin_buffer_print( gentity_t *ent, char *m )
{
  // 1022 - strlen("print 64 \"\"") - 1
  if( strlen( m ) + strlen( g_bfb ) >= 1009 )
  {
    ADMP( g_bfb );
    g_bfb[ 0 ] = '\0';
  }
  Q_strcat( g_bfb, sizeof( g_bfb ), m );
}


void G_admin_cleanup()
{
  int i = 0;

  for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
  {
    G_Free( g_admin_levels[ i ] );
    g_admin_levels[ i ] = NULL;
    g_admin_number_levels = 0;
  }
  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
  {
    G_Free( g_admin_admins[ i ] );
    g_admin_admins[ i ] = NULL;
    g_admin_number_admins = 0;
  }
  for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
  {
    G_Free( g_admin_bans[ i ] );
    g_admin_bans[ i ] = NULL;
    g_admin_number_bans = 0;
  }
  for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_scripts[ i ]; i++ )
  {
    G_Free( g_admin_scripts[ i ] );
    g_admin_scripts[ i ] = NULL;
    g_admin_number_cmds = 0;
  }
}

qboolean G_admin_L1(gentity_t *ent, int skiparg ){
  int pids[ MAX_CLIENTS ];
  char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];
  int minargc;
  gentity_t *vic;

  minargc = 2 + skiparg;

  if( G_SayArgc() < minargc )
  {
    ADMP( "^3!L1: ^7usage: !L1 [name]\n" );
    return qfalse;
  }
  G_SayArgv( 1 + skiparg, name, sizeof( name ) );
  reason = G_SayConcatArgs( 2 + skiparg );
  if( G_ClientNumbersFromString( name, pids ) != 1 )
  {
    G_MatchOnePlayer( pids, err, sizeof( err ) );
    ADMP( va( "^3!L1: ^7%s\n", err ) );
    return qfalse;
  }
  if( G_admin_level(&g_entities[ pids[ 0 ] ] )>0 )
  {
    ADMP( "^3!L1: ^7sorry, but that person is already higher than"
        " level 0.\n" );
    return qfalse;
  }
 
  vic = &g_entities[ pids[ 0 ] ];
  //AP(va( "!setlevel %s 1\n",vic->client->pers.netname));
  trap_SendConsoleCommand( EXEC_APPEND,va( "!setlevel %d 1;", pids[ 0 ] ) );
  ClientUserinfoChanged( pids[ 0 ] );
  return qtrue;
}
